diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..502564d --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Build directory +/build/ + +# Built binary and maps +/maps/ +/ttrts + +# user files +*.user +*.sublime* +*.idea +*~ + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..97a47ba --- /dev/null +++ b/CMakeLists.txt @@ -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 ) diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6b299b --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# TTRTS +*The Tiny Terminal RTS where the players write their AIs* + +----------------------------------------------------------- +## Introduction +A simple terminal based RTS game that uses text files to communicate game state and unit commands. + +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 + +#### Requirements +* CMake - our build system uses cmake +* Linux/OSX - currently no support for Windows, tracked with [Issue #9](https://github.com/mdiluz/ttrts/issues/9) + +#### To Build + $ git clone https://github.com/mdiluz/ttrts.git + $ cd ttrts + $ ./bootstrap.sh + $ ./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 + +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 diff --git a/api/perl/ttrts.pm b/api/perl/ttrts.pm new file mode 100644 index 0000000..793269b --- /dev/null +++ b/api/perl/ttrts.pm @@ -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 = ; + 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 = ; + 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 = ; + 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 = ; + 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; \ No newline at end of file diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..701f47b --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,34 @@ +#! /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" +test ! -e build && mkdir build +cd build/ +cmake .. +if [[ $? != 0 ]]; then + echo "TTRTS: CMake failed, exiting Bootstrap" + exit +fi + +echo "TTRTS: Performing install" +sudo make install +if [[ $? != 0 ]]; then + echo "TTRTS: Install failed, check output" + exit +fi + +# Run final test to make sure +echo "TTRTS: Running tests" +./source/test/ttrts-test +if [[ $? != 0 ]]; then + echo "TTRTS: Tests failed, build must be broken" + exit +fi + +echo "TTRTS: Bootstrap complete" diff --git a/fonts/opensans-bold-webfont.eot b/fonts/opensans-bold-webfont.eot deleted file mode 100644 index b5bad08..0000000 Binary files a/fonts/opensans-bold-webfont.eot and /dev/null differ diff --git a/fonts/opensans-bold-webfont.svg b/fonts/opensans-bold-webfont.svg deleted file mode 100644 index 1557f68..0000000 --- a/fonts/opensans-bold-webfont.svg +++ /dev/null @@ -1,251 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Digitized data copyright 20102011 Google Corporation -Foundry : Ascender Corporation -Foundry URL : httpwwwascendercorpcom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fonts/opensans-bold-webfont.ttf b/fonts/opensans-bold-webfont.ttf deleted file mode 100644 index 338220f..0000000 Binary files a/fonts/opensans-bold-webfont.ttf and /dev/null differ diff --git a/fonts/opensans-bold-webfont.woff b/fonts/opensans-bold-webfont.woff deleted file mode 100644 index ea6007b..0000000 Binary files a/fonts/opensans-bold-webfont.woff and /dev/null differ diff --git a/fonts/opensans-bolditalic-webfont.eot b/fonts/opensans-bolditalic-webfont.eot deleted file mode 100644 index d892fd9..0000000 Binary files a/fonts/opensans-bolditalic-webfont.eot and /dev/null differ diff --git a/fonts/opensans-bolditalic-webfont.svg b/fonts/opensans-bolditalic-webfont.svg deleted file mode 100644 index 24661f3..0000000 --- a/fonts/opensans-bolditalic-webfont.svg +++ /dev/null @@ -1,251 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Digitized data copyright 20102011 Google Corporation -Foundry : Ascender Corporation -Foundry URL : httpwwwascendercorpcom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fonts/opensans-bolditalic-webfont.ttf b/fonts/opensans-bolditalic-webfont.ttf deleted file mode 100644 index b3eb0d3..0000000 Binary files a/fonts/opensans-bolditalic-webfont.ttf and /dev/null differ diff --git a/fonts/opensans-bolditalic-webfont.woff b/fonts/opensans-bolditalic-webfont.woff deleted file mode 100644 index 1712e15..0000000 Binary files a/fonts/opensans-bolditalic-webfont.woff and /dev/null differ diff --git a/fonts/opensans-extrabold-webfont.eot b/fonts/opensans-extrabold-webfont.eot deleted file mode 100644 index 0e88f02..0000000 Binary files a/fonts/opensans-extrabold-webfont.eot and /dev/null differ diff --git a/fonts/opensans-extrabold-webfont.svg b/fonts/opensans-extrabold-webfont.svg deleted file mode 100644 index c3d6642..0000000 --- a/fonts/opensans-extrabold-webfont.svg +++ /dev/null @@ -1,251 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Digitized data copyright 2011 Google Corporation -Foundry : Ascender Corporation -Foundry URL : httpwwwascendercorpcom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fonts/opensans-extrabold-webfont.ttf b/fonts/opensans-extrabold-webfont.ttf deleted file mode 100644 index bec521d..0000000 Binary files a/fonts/opensans-extrabold-webfont.ttf and /dev/null differ diff --git a/fonts/opensans-extrabold-webfont.woff b/fonts/opensans-extrabold-webfont.woff deleted file mode 100644 index a24b205..0000000 Binary files a/fonts/opensans-extrabold-webfont.woff and /dev/null differ diff --git a/fonts/opensans-italic-webfont.eot b/fonts/opensans-italic-webfont.eot deleted file mode 100644 index 3593c12..0000000 Binary files a/fonts/opensans-italic-webfont.eot and /dev/null differ diff --git a/fonts/opensans-italic-webfont.svg b/fonts/opensans-italic-webfont.svg deleted file mode 100644 index 537d20c..0000000 --- a/fonts/opensans-italic-webfont.svg +++ /dev/null @@ -1,251 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Digitized data copyright 20102011 Google Corporation -Foundry : Ascender Corporation -Foundry URL : httpwwwascendercorpcom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fonts/opensans-italic-webfont.ttf b/fonts/opensans-italic-webfont.ttf deleted file mode 100644 index ddc75c6..0000000 Binary files a/fonts/opensans-italic-webfont.ttf and /dev/null differ diff --git a/fonts/opensans-italic-webfont.woff b/fonts/opensans-italic-webfont.woff deleted file mode 100644 index 302cb00..0000000 Binary files a/fonts/opensans-italic-webfont.woff and /dev/null differ diff --git a/fonts/opensans-regular-webfont.eot b/fonts/opensans-regular-webfont.eot deleted file mode 100644 index 1c64986..0000000 Binary files a/fonts/opensans-regular-webfont.eot and /dev/null differ diff --git a/fonts/opensans-regular-webfont.svg b/fonts/opensans-regular-webfont.svg deleted file mode 100644 index ead219a..0000000 --- a/fonts/opensans-regular-webfont.svg +++ /dev/null @@ -1,252 +0,0 @@ - - - - -This is a custom SVG webfont generated by Font Squirrel. -Copyright : Digitized data copyright 20102011 Google Corporation -Foundry : Ascender Corporation -Foundry URL : httpwwwascendercorpcom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/fonts/opensans-regular-webfont.ttf b/fonts/opensans-regular-webfont.ttf deleted file mode 100644 index 99a1ece..0000000 Binary files a/fonts/opensans-regular-webfont.ttf and /dev/null differ diff --git a/fonts/opensans-regular-webfont.woff b/fonts/opensans-regular-webfont.woff deleted file mode 100644 index 826d643..0000000 Binary files a/fonts/opensans-regular-webfont.woff and /dev/null differ diff --git a/images/bg-ramp.jpg b/images/bg-ramp.jpg deleted file mode 100644 index 7738563..0000000 Binary files a/images/bg-ramp.jpg and /dev/null differ diff --git a/images/blockquote-gfx-2x.png b/images/blockquote-gfx-2x.png deleted file mode 100644 index 993efd5..0000000 Binary files a/images/blockquote-gfx-2x.png and /dev/null differ diff --git a/images/blockquote-gfx.png b/images/blockquote-gfx.png deleted file mode 100644 index bd5be35..0000000 Binary files a/images/blockquote-gfx.png and /dev/null differ diff --git a/images/chevron-2x.png b/images/chevron-2x.png deleted file mode 100644 index 0ae935e..0000000 Binary files a/images/chevron-2x.png and /dev/null differ diff --git a/images/chevron.png b/images/chevron.png deleted file mode 100644 index 65cfeda..0000000 Binary files a/images/chevron.png and /dev/null differ diff --git a/images/download-fallback-bg.png b/images/download-fallback-bg.png deleted file mode 100644 index aedafab..0000000 Binary files a/images/download-fallback-bg.png and /dev/null differ diff --git a/images/download-sprite.png b/images/download-sprite.png deleted file mode 100644 index 56eedc4..0000000 Binary files a/images/download-sprite.png and /dev/null differ diff --git a/images/footer-ramp.jpg b/images/footer-ramp.jpg deleted file mode 100644 index 99accec..0000000 Binary files a/images/footer-ramp.jpg and /dev/null differ diff --git a/images/fork-sprite.png b/images/fork-sprite.png deleted file mode 100644 index 0ce61b2..0000000 Binary files a/images/fork-sprite.png and /dev/null differ diff --git a/images/hr-2x.jpg b/images/hr-2x.jpg deleted file mode 100644 index a883d5d..0000000 Binary files a/images/hr-2x.jpg and /dev/null differ diff --git a/images/hr.jpg b/images/hr.jpg deleted file mode 100644 index 2dc7cef..0000000 Binary files a/images/hr.jpg and /dev/null differ diff --git a/images/octocat-2x.png b/images/octocat-2x.png deleted file mode 100644 index f995921..0000000 Binary files a/images/octocat-2x.png and /dev/null differ diff --git a/images/octocat.png b/images/octocat.png deleted file mode 100644 index 7c55dfc..0000000 Binary files a/images/octocat.png and /dev/null differ diff --git a/images/ribbon-tail-sprite-2x.png b/images/ribbon-tail-sprite-2x.png deleted file mode 100644 index 46357a0..0000000 Binary files a/images/ribbon-tail-sprite-2x.png and /dev/null differ diff --git a/images/ribbon-tail-sprite.png b/images/ribbon-tail-sprite.png deleted file mode 100644 index bd627d9..0000000 Binary files a/images/ribbon-tail-sprite.png and /dev/null differ diff --git a/images/shield-fallback.png b/images/shield-fallback.png deleted file mode 100644 index be799b2..0000000 Binary files a/images/shield-fallback.png and /dev/null differ diff --git a/images/shield.png b/images/shield.png deleted file mode 100644 index 224425e..0000000 Binary files a/images/shield.png and /dev/null differ diff --git a/images/site-2.png b/images/site-2.png deleted file mode 100644 index 7c55dfc..0000000 Binary files a/images/site-2.png and /dev/null differ diff --git a/images/small-ribbon-tail-sprite-2x.png b/images/small-ribbon-tail-sprite-2x.png deleted file mode 100644 index 482649d..0000000 Binary files a/images/small-ribbon-tail-sprite-2x.png and /dev/null differ diff --git a/images/small-ribbon-tail-sprite.png b/images/small-ribbon-tail-sprite.png deleted file mode 100644 index f717af6..0000000 Binary files a/images/small-ribbon-tail-sprite.png and /dev/null differ diff --git a/index.html b/index.html deleted file mode 100644 index d3d2cf4..0000000 --- a/index.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - - - - - - - - - - - TTRTS by mdiluz - - - - View on GitHub -
- -
- - -

TTRTS

-

The Tiny Terminal RTS where the players write their AIs

-
- - -
-
- -
- - download .ZIPdownload .TGZ - -
- - - - - -
-

-TTRTS

- -

The Tiny Terminal RTS where the players write their AIs

- -
- -

-Introduction

- -

A simple terminal based RTS game that uses text files to communicate game state and unit commands.

- -

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

- -

-Requirements

- -
    -
  • CMake - our build system uses cmake
  • -
  • Linux/OSX - currently no support for Windows, tracked with Issue #9 -
  • -
  • perl 5.0 or newer - for the launch script
  • -
- -

-To Build

- -
$ git clone https://github.com/mdiluz/ttrts.git
-$ cd ttrts
-$ ./bootstrap.sh
-$ man ttrts # for full usage and guide
-
- -
- -

-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 -
  • -
- -

-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

- -

See the ttrts README for full usage and game rules

- -

See ttrts-players for examples of AIs

-
- - - -
- - - - diff --git a/javascripts/headsmart.min.js b/javascripts/headsmart.min.js deleted file mode 100644 index 16da97a..0000000 --- a/javascripts/headsmart.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(a){a.fn.headsmart=function(){var c=a(this);d();function d(){var e=[],g="";if(b("h1")){e.push("h1")}if(b("h2")){e.push("h2")}if(b("h3")){e.push("h3")}if(b("h4")){e.push("h4")}if(b("h5")){e.push("h5")}if(b("h6")){e.push("h6")}for(var f=0;f0)?true:false}}})(jQuery); \ No newline at end of file diff --git a/javascripts/main.js b/javascripts/main.js deleted file mode 100644 index d8135d3..0000000 --- a/javascripts/main.js +++ /dev/null @@ -1 +0,0 @@ -console.log('This would be the main JS file.'); diff --git a/javascripts/modernizr.js b/javascripts/modernizr.js deleted file mode 100644 index 434b0af..0000000 --- a/javascripts/modernizr.js +++ /dev/null @@ -1,4 +0,0 @@ -/* Modernizr 2.5.2 (Custom Build) | MIT & BSD - * Build: http://www.modernizr.com/download/#-fontface-borderradius-boxshadow-textshadow-cssgradients-shiv-cssclasses-teststyles-testprop-testallprops-prefixes-domprefixes-load - */ -;window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.5.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["­",""].join(""),k.id=h,m.innerHTML+=f,m.appendChild(k),l||g.appendChild(m),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e});var G=function(a,c){var d=a.join(""),f=c.length;w(d,function(a,c){var d=b.styleSheets[b.styleSheets.length-1],g=d?d.cssRules&&d.cssRules[0]?d.cssRules[0].cssText:d.cssText||"":"",h=a.childNodes,i={};while(f--)i[h[f].id]=h[f];e.fontface=/src/i.test(g)&&g.indexOf(c.split(" ")[0])===0},f,c)}(['@font-face {font-family:"font";src:url("https://")}'],["fontface"]);q.borderradius=function(){return F("borderRadius")},q.boxshadow=function(){return F("boxShadow")},q.textshadow=function(){return b.createElement("div").style.textShadow===""},q.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return z((a+"-webkit- ".split(" ").join(b+a)+m.join(c+a)).slice(0,-a.length)),C(j.backgroundImage,"gradient")},q.fontface=function(){return e.fontface};for(var H in q)y(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return z(""),i=k=null,function(a,b){function g(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){function m(){var a=j.cloneNode(!1);return k.shivMethods?(i(a),a):a}function n(a){var b=(c[a]||(c[a]=e(a))).cloneNode(!1);return k.shivMethods&&!d.test(a)?j.appendChild(b):b}var b,c={},e=a.createElement,f=a.createDocumentFragment,g=h(),j=f(),l=g.length;while(l--)b=g[l],c[b]=e(b),j.createElement(b);a.createElement=n,a.createDocumentFragment=m}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),k.shivMethods&&!f&&(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|iframe|input|script|textarea)$/i,e,f;(function(){var c,d=b.createElement("a"),g=a.getComputedStyle,h=b.documentElement,i=b.body||(c=h.insertBefore(b.createElement("body"),h.firstChild));i.insertBefore(d,i.firstChild),d.hidden=!0,d.innerHTML="",e=(d.currentStyle||g(d,null)).display=="none",f=d.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}(),i.removeChild(d),c&&h.removeChild(c)})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=!!b.attachEvent,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f $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 diff --git a/scripts/gen_maps.sh b/scripts/gen_maps.sh new file mode 100755 index 0000000..bb02331 --- /dev/null +++ b/scripts/gen_maps.sh @@ -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 \ No newline at end of file diff --git a/scripts/gen_usage.sh b/scripts/gen_usage.sh new file mode 100755 index 0000000..5e33166 --- /dev/null +++ b/scripts/gen_usage.sh @@ -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 diff --git a/scripts/gen_version_header.sh b/scripts/gen_version_header.sh new file mode 100755 index 0000000..aec60e2 --- /dev/null +++ b/scripts/gen_version_header.sh @@ -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 diff --git a/scripts/ttrts_complete b/scripts/ttrts_complete new file mode 100755 index 0000000..d257ed4 --- /dev/null +++ b/scripts/ttrts_complete @@ -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 diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt new file mode 100644 index 0000000..d73e509 --- /dev/null +++ b/source/CMakeLists.txt @@ -0,0 +1,13 @@ +# Main libraries +add_subdirectory( ttrts ) +add_subdirectory( system ) + +# Main binaries +add_subdirectory( launcher ) +add_subdirectory( client ) +add_subdirectory( server ) +add_subdirectory( local ) + +# Auxhilary binaries +add_subdirectory( test ) +add_subdirectory( gen ) \ No newline at end of file diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000..22b8dea --- /dev/null +++ b/source/README.md @@ -0,0 +1,121 @@ +# NAME + ttrts - Tiny Terminal RTS + +# SYNOPSIS + ttrts [--server] [--client] [--host=HOSTNAME] [--map=MAPFILE] + +# DESCRIPTION + ttrts is a tiny terminal based RTS that uses text files as order lists to control the units + + This means that any user, program or cat that can read and write to text files can play the game + +# RETURN VALUE + ttrts will return -1 on error, or the winning player on completion + +# OPTIONS + --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. diff --git a/source/client/CMakeLists.txt b/source/client/CMakeLists.txt new file mode 100644 index 0000000..1ae8054 --- /dev/null +++ b/source/client/CMakeLists.txt @@ -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 ) diff --git a/source/client/client.cpp b/source/client/client.cpp new file mode 100644 index 0000000..116a5a0 --- /dev/null +++ b/source/client/client.cpp @@ -0,0 +1,69 @@ +#include + +#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 "<= 0 ) + { + std::cout<<"TTRTS: Waiting for gamestate"< +#include +#include "formatters.h" + +void AddUnitToGame( player_t player, char vis, uvector2 vec, CTTRTSGame& game ) +{ + CUnit unit = CUnit::GetUnitFromVis(vis); + unit.SetPos(vec); + unit.SetPlayer(player); + game.AddUnit(std::move(unit)); +} + +void OutputGame( CTTRTSGame&& game ) +{ + std::ofstream output; + output.open (game.GetName() + ".txt"); + output << GetStringFromGame(game); + output.close(); + + __forceResetCUnitID(); +} + +int main() +{ + // Tiny 1v1 Game + //------ + //-G---- + //----R- + //-G---- + //----R- + //------ + { + CTTRTSGame game(6, 6); + game.SetName("Tiny2Player"); + + AddUnitToGame( player_t::Red, '<', uvector2(4, 2), game); + AddUnitToGame( player_t::Red, '<', uvector2(4, 4), game); + AddUnitToGame( player_t::Green, '>', uvector2(1, 1), game); + AddUnitToGame( player_t::Green, '>', uvector2(1, 3), game); + + game.AddWall(uvector2(3,2)); + game.AddWall(uvector2(3,3)); + + OutputGame(std::move(game)); + } + + // Basic 1v1 game + // -------------------- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -G------------------ + // ------------------R- + // -------------------- + { + CTTRTSGame game(20, 12); + game.SetName("Big2Player"); + + for ( ucoord_t y : { 2,4,6,8,10 } ) + AddUnitToGame( player_t::Red, '<', uvector2(18, y), game); + for ( ucoord_t y : { 1,3,5,7,9 } ) + AddUnitToGame( player_t::Green, '>', uvector2(1, y), game); + + + OutputGame(std::move(game)); + } + + // Sort of like Chess + //GG------ + //------RR + //GG------ + //------RR + //GG------ + //------RR + //GG------ + //------RR + { + CTTRTSGame game(8, 8); + game.SetName("Chess"); + + for ( ucoord_t y : { 1,3,5,7 } ) { + AddUnitToGame(player_t::Red, '<', uvector2(6, y), game); + AddUnitToGame(player_t::Red, '<', uvector2(7, y), game); + } + + for ( ucoord_t y : { 0,2,4,6 } ) { + AddUnitToGame(player_t::Green, '>', uvector2(0, y), game); + AddUnitToGame(player_t::Green, '>', uvector2(1, y), 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)); + } +} \ No newline at end of file diff --git a/source/launcher/CMakeLists.txt b/source/launcher/CMakeLists.txt new file mode 100644 index 0000000..c6726c7 --- /dev/null +++ b/source/launcher/CMakeLists.txt @@ -0,0 +1,6 @@ +# ====================== ttrts ======================= +# Project name +project( ttrts-perl-launch ) + +# Add bash completion to install +install( PROGRAMS ttrts.pl DESTINATION bin RENAME ttrts ) diff --git a/source/launcher/ttrts.pl b/source/launcher/ttrts.pl new file mode 100755 index 0000000..886fed6 --- /dev/null +++ b/source/launcher/ttrts.pl @@ -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 \ No newline at end of file diff --git a/source/local/CMakeLists.txt b/source/local/CMakeLists.txt new file mode 100644 index 0000000..172b736 --- /dev/null +++ b/source/local/CMakeLists.txt @@ -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 ) \ No newline at end of file diff --git a/source/local/local.cpp b/source/local/local.cpp new file mode 100644 index 0000000..b2697e7 --- /dev/null +++ b/source/local/local.cpp @@ -0,0 +1,62 @@ +#include "game.h" +#include "filesystem.h" +#include "error.h" +#include "net.h" + +#include + +// ===================================================================================================================== +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 "< +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "net.h" +#include "filesystem.h" + +void RunServerForGame(CTTRTSGame &game) +{ + std::cout<<"TTRTS: Setting up server"< players = game.GetPlayers(); + unsigned int numClients = players.size(); + auto player_iterator = players.begin(); + + // game mutex + std::mutex gameMutex; + + // Set of clients + std::vector myClients; + + std::cout<<"TTRTS: Waiting for "< +#include +#include + +//====================================================================================================================== +// Error functions + +// For local fatal errors +inline void fatal_error(const char *msg) +{ + std::cerr< +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// ===================================================================================================================== +// 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<(file)),std::istreambuf_iterator()); + + 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(turnFile)), std::istreambuf_iterator()); + 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"<>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 < +#include + +#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 \ No newline at end of file diff --git a/source/system/net.cpp b/source/system/net.cpp new file mode 100644 index 0000000..29c23b9 --- /dev/null +++ b/source/system/net.cpp @@ -0,0 +1,261 @@ +#include "net.h" +#include "error.h" + +#include + +#include +#include + +#include +#include +#include +#include + +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 "< &myClients, CTTRTSGame &game, std::mutex &gameMutex) +{ + // Spawn threads + std::vector 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 &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 "<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!"< +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#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 &myClients, CTTRTSGame &game, std::mutex &gameMutex); + +// Sends current gamestate to each client +void SendGamestateToClients(std::vector &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 \ No newline at end of file diff --git a/source/test/CMakeLists.txt b/source/test/CMakeLists.txt new file mode 100644 index 0000000..3e6a60c --- /dev/null +++ b/source/test/CMakeLists.txt @@ -0,0 +1,17 @@ + +# ====================== tests ======================= +# Project name +project( ttrts-test ) + +include_directories( + ../ttrts +) + +set( SOURCES + test.cpp +) + +# Add the executable +add_executable( ttrts-test ${SOURCES} ) + +target_link_libraries( ttrts-test ttrts ) diff --git a/source/test/test.cpp b/source/test/test.cpp new file mode 100644 index 0000000..5cbb700 --- /dev/null +++ b/source/test/test.cpp @@ -0,0 +1,205 @@ +#include +#include // std::cout + +#include "order.h" +#include "game.h" + +const char* tests() +{ + // Test if we can properly set a unit's visual + { + CUnit unit; + unit.SetFromVisual('v'); + if(unit.GetVisual() != 'v' ) + return "failed to properly create V unit"; + } + + // Test unique unit IDs + { + CUnit unit; + CUnit unit2; + if(unit.GetID() == unit2.GetID() ) + return "Unit IDs the same"; + } + + // Test basic invalid unit conversion + { + CUnit unit1; + + std::string unit1Desc = GetStringFromUnit(unit1); + CUnit unit2 = GetUnitFromString(unit1Desc); + + if ( unit1 != unit2 ) + return "Failed to convert an empty unit to string and back"; + } + + // Test custom unit conversion + { + CUnit unit1; + unit1.SetFromVisual('v'); + unit1.SetPlayer(player_t::Green); + unit1.SetPos(uvector2(5, 10)); + + std::string unit1Desc = GetStringFromUnit(unit1); + CUnit unit2 = GetUnitFromString(unit1Desc); + + if ( unit1 != unit2 ) + return "Failed to convert custom unit to string and back"; + } + + // Test if we can successfully create a unit from a visual + { + CUnit unit = CUnit::GetUnitFromVis('v'); + if(unit.GetVisual() != 'v' ) + return "failed to properly create V unit with factory"; + } + + // Test if we can successfully convert orders back and forth + { + SOrder order; + order.command = command_c::F; + order.unit = 10; + std::string order_string = GetStringFromOrder(order); + SOrder order2 = GetOrderFromString(order_string); + + if ( order2 != order ) + return "failed order string conversion test"; + } + + // Test of the game can logically handle a blank game + { + CTTRTSGame game( 15, 10 ); + if( game.SimulateToNextTurn() ) + return "Failed to simulate a blank game"; + + if( game.GetNumUnits() ) + return "Game started with non-zero unit number"; + } + + // Test if the game correctly rejects units placed ontop of others + { + CTTRTSGame game( 5, 5 ); + + { + CUnit unit = CUnit::GetUnitFromVis('^'); + unit.SetPos({2, 2}); + unit.SetPlayer(player_t::Red); + + game.AddUnit(std::move(unit)); + } + + { + CUnit unit = CUnit::GetUnitFromVis('^'); + unit.SetPos({2, 2}); + unit.SetPlayer(player_t::Red); + + if( !game.AddUnit(std::move(unit)) ) + return "Game should have rejected unit placed on the same spot"; + + if( game.GetNumUnits() != 1 ) + return "Game ended up with too many units"; + } + } + + // Test on a small board if a movement command succeeds correctly + { + CTTRTSGame game( 5, 5 ); + + CUnit unit = CUnit::GetUnitFromVis('>'); + const unit_id_t id = unit.GetID(); + SOrder order; + + unit.SetPos({2, 2}); + unit.SetPlayer(player_t::Red); + + if ( game.AddUnit(std::move(unit)) ) + return "Game failed to add valid unit"; + + order.unit = id; + order.command = command_c::F; + + if( game.IssueOrder(player_t::Red,order) ) + return "Game failed to issue valid order"; + + if (game.SimulateToNextTurn() ) + return "Game failed to simulate valid turn"; + + if(game.GetUnitByIDConst(id).GetPos() != uvector2{3,2} ) + return "Simple movement order failed"; + + } + + // Test on a tiny board, whether a unit can correctly attack another + { + CTTRTSGame game( 2, 1 ); + game.SetName("Test_578"); + + unit_id_t id; + { + CUnit unit = CUnit::GetUnitFromVis('>'); + id = unit.GetID(); + SOrder order; + + unit.SetPos({0, 0}); + unit.SetPlayer(player_t::Blue); + + if ( game.AddUnit(std::move(unit)) ) + return "Game failed to add valid unit"; + + order.unit = id; + order.command = command_c::A; + + if( game.IssueOrder(player_t::Blue,order) ) + return "Game failed to issue valid order"; + } + { + CUnit unit = CUnit::GetUnitFromVis('<'); + + unit.SetPos({1, 0}); + unit.SetPlayer(player_t::Red); + + if ( game.AddUnit(std::move(unit)) ) + return "Game failed to add valid unit"; + } + + game.SimulateToNextTurn(); + + if ( game.GetNumUnits() != 1 ) + return "Game failed to kill a unit when it logically should have"; + + if (game.GetUnitByIndex(0).GetDir() != dir_c::E ) + return "Game killed the wrong unit"; + + if (game.GetUnitByIndex(0).GetID() != id ) + return "Game killed the wrong unit"; + + if ( game.GetWinningPlayer() != player_t::Blue ) + return "Game failed to recognise a win for the right Player"; + + std::string game_string = GetStringFromGame(game); + CTTRTSGame game2 = GetGameFromString(game_string); + + std::string game2_string = GetStringFromGame(game2); + + // Try matching up the game descriptors + if( game_string != game2_string ) + return "Generating new game from string failed"; + + } + + return 0; +} + +// Main program entry point +int main() +{ + const char* res = tests(); + + if( res ) + { + std::cout<<"ERROR: "< + +#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"< 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; +} diff --git a/source/ttrts/formatters.h b/source/ttrts/formatters.h new file mode 100644 index 0000000..31cf9e7 --- /dev/null +++ b/source/ttrts/formatters.h @@ -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 \ No newline at end of file diff --git a/source/ttrts/game.cpp b/source/ttrts/game.cpp new file mode 100644 index 0000000..d649060 --- /dev/null +++ b/source/ttrts/game.cpp @@ -0,0 +1,532 @@ +#include "game.h" + +#include +#include +#include "formatters.h" + +CTTRTSGame::CTTRTSGame( ucoord_t c, ucoord_t r ) +: dimensions( c,r ) +, turn (0), name ( "Custom_Game" ) +{ + +} + +// Move constructor +CTTRTSGame::CTTRTSGame(CTTRTSGame&& game) +: m_OrderUnitPairs(std::move(game.m_OrderUnitPairs)) +, dimensions(std::move(game.dimensions)) +, turn(std::move(game.turn)) +, name(std::move(game.name)) +, m_walls(std::move(game.m_walls)) +{ + +} + +CTTRTSGame& CTTRTSGame::operator=(CTTRTSGame&& game) +{ + m_OrderUnitPairs = std::move(game.m_OrderUnitPairs); + dimensions = std::move(game.dimensions); + turn = std::move(game.turn); + name = std::move(game.name); + return *this; +} + +// Interpret a string of orders +int CTTRTSGame::IssueOrders( player_t player, const std::string& _orders ) +{ + COrderVector orderVector; + + // Copy the const orders into a buffer we can edit + std::string orders = _orders; + + // Find a line end + size_t pos; + while ( (pos = orders.find('\n')) != std::string::npos ) + { + // Grab the string up to the line end + const std::string sorder = orders.substr(0, pos); + + // Erase all of string up to and including the line end + orders.erase(0,pos+1); + + // Create an order from the string and push it back + SOrder order = GetOrderFromString( sorder ); + orderVector.push_back(order); + } + + // Call our add order by vector method + return IssueOrders(player,orderVector); +} + +// Issue orders by vector to the game +int CTTRTSGame::IssueOrders( player_t player, const COrderVector& orders ) +{ + // verify all the orders + for ( auto order : orders ) + { + // If any order returns non-zero, back out + if ( IssueOrder(player,order) ) + return 1; + } + + return 0; +} + +// Issue a single order +int CTTRTSGame::IssueOrder( player_t player, const SOrder & order ) +{ + // Verify the order + if ( VerifyOrder(player,order) ) + return 1; + + // Get the right unit for the order + for ( SOrderUnitPair & pair : m_OrderUnitPairs ) + { + if (pair.unit.GetID() == order.unit ) + { + pair.order = order; + return 0; + } + } + + // Unit was not found, return 2 + return 2; +} + +// Verify a position +int CTTRTSGame::VerifyPosIsValidMovement(uvector2 vec) const +{ + // Simply check if within the bounds of our dimensions for now + if ( ( vec.x >= dimensions.x ) + || ( vec.y >= dimensions.y ) ) + { + return 1; + } + + // Check within our invalid positions + for ( const uvector2& invalid : m_walls) + { + if( vec == invalid ) + { + return 2; + } + } + + return 0; +} + + +// Get a units new position +uvector2 CTTRTSGame::GetNewPosition( const SOrderUnitPair & pair ) const +{ + // Grab the order + switch ( pair.order.command) + { + // For forward orders, grab in front + case command_c::F: + return pair.unit.GetInFront(); + // For all other orders, just grab the old position + default: + return pair.unit.GetPos(); + } +} + +// Simulate and progress to the next turn +// Returns non-zero if simulation failed +int CTTRTSGame::SimulateToNextTurn() +{ + int error = 0; + + // Attempt all movement orders + for ( SOrderUnitPair & pair : m_OrderUnitPairs ) + { + switch ( pair.order.command) + { + case command_c::F: + { + // Verify new unit position will be on the board + uvector2 newpos = GetNewPosition(pair); + + // Verify the position is even available + bool possible = (VerifyPosIsValidMovement(newpos) == 0 ); + + if ( possible ) + { + // If any unit is in this spot, or moving unit moving to said spot, reject this + for ( const SOrderUnitPair & pair2 : m_OrderUnitPairs ) + { + // Skip myself + if(pair.unit.GetID() == pair2.unit.GetID() ) continue; + + if( GetNewPosition(pair2) == newpos ) + { + possible = false; + break; + } + } + } + + // If the movement is still possible + if ( possible ) + { + // Create a wall at our old position + AddWall(pair.unit.GetPos()); + pair.unit.SetPos(newpos); + } + } + break; + default: + break; + } + } + + // Turn all units that need turning + for ( SOrderUnitPair & pair : m_OrderUnitPairs ) + { + switch ( pair.order.command) + { + case command_c::L: + { + // Simply turn left + pair.unit.TurnLeft(); + } + break; + case command_c::R: + { + // Simply turn right + pair.unit.TurnRight(); + } + break; + default: + break; + } + } + + // Iterate through all charge states + bool charging = true; + while(charging) + { + // Assume no more charging + charging = false; + // Initially move all units + for ( SOrderUnitPair & pair : m_OrderUnitPairs ) + { + if ( pair.order.command == command_c::A ) + { + uvector2 newpos = pair.unit.GetInFront(); + // If move would be within the arena + if (VerifyPosIsValidMovement(newpos) == 0 ) + { + pair.unit.SetPos(newpos); + + // Unit moved, so more charging needs to be done + charging = true; + } + } + } + + std::vector< unit_id_t > toKill; // Vector to store which units to kill + + // Initially move all units to check for pass through + for ( SOrderUnitPair & pair1 : m_OrderUnitPairs ) + if ( pair1.order.command == command_c::A ) + for ( SOrderUnitPair & pair2 : m_OrderUnitPairs ) + if (pair1.unit.GetID() != pair2.unit.GetID() // Don't check the same units + && pair2.order.command == command_c::A ) + { + if( CheckForPassThrough(pair1.unit,pair2.unit) ) + { + toKill.push_back(pair1.unit.GetID()); + toKill.push_back(pair2.unit.GetID()); + } + } + + // Kill all units to kill + KillAll(toKill); + toKill.clear(); + + // Check for all matching spots + for ( SOrderUnitPair & pair1 : m_OrderUnitPairs ) + for ( SOrderUnitPair & pair2 : m_OrderUnitPairs ) + { + if(pair1.unit.GetID() == pair2.unit.GetID() ) continue; // Don't check the same units + + if(pair1.unit.GetPos() == pair2.unit.GetPos() ) + { + if( pair1.order.command == command_c::A ) + { + toKill.push_back(pair2.unit.GetID()); + } + + if( pair2.order.command == command_c::A ) + { + toKill.push_back(pair1.unit.GetID()); + } + } + } + + // Kill all units to kill + KillAll(toKill); + toKill.clear(); + } + + // Clear all orders + for ( SOrderUnitPair & pair : m_OrderUnitPairs ) + pair.order = SOrder(); + + // Increment the current turn + turn++; + + return error; +} + + +// Kill all units in list +void CTTRTSGame::KillAll( std::vector< unit_id_t >& vec ) +{ + // Sort and erase all duplicates + std::sort( vec.begin(), vec.end() ); + vec.erase( std::unique( vec.begin(), vec.end() ), vec.end() ); + for ( auto id : vec ) + { + for ( OrderUnitPairVector::iterator it = m_OrderUnitPairs.begin(); + it != m_OrderUnitPairs.end(); + it++ ) + { + if((*it).unit.GetID() == id ) + { + // Remove the unit from our alive unit pairs + m_OrderUnitPairs.erase(it); + break; + + } + } + } +} + +// Check if two units passed through each other +bool CTTRTSGame::CheckForPassThrough( const CUnit& one, const CUnit& two ) +{ + uvector2 pos1 = one.GetPos(); + uvector2 pos2 = two.GetPos(); + dir_c dir1 = one.GetDir(); + dir_c dir2 = two.GetDir(); + + if( pos1.x == pos2.x ) { // Same col + if (pos1.y == (pos2.y + 1)) { + if (dir1 == dir_c::N && dir2 == dir_c::S) + return true; + } + else if (pos1.y == (pos2.y - 1)) { + if (dir1 == dir_c::S && dir2 == dir_c::N) + return true; + } + } + else if( pos1.y == pos2.y ) { // Same row + if( pos1.x == (pos2.x+1) ) { + if( dir1 == dir_c::E && dir2 == dir_c::W ) + return true; + } + else if( pos1.x == (pos2.x-1) ) { + if( dir1 == dir_c::E && dir2 == dir_c::W ) + return true; + } + } + + return false; +} + +// Add a unit, nonzero return value indicates error +int CTTRTSGame::AddUnit( CUnit&& unit ) +{ + // Verify the unit + if( !unit.Valid() ) + return 1; + + // Verify if the unit can be placed on the current board + const uvector2 pos = unit.GetPos(); + if( (pos.x >= dimensions.x) || (pos.y >= dimensions.y) ) + return 2; + + // If any unit's position matches, reject this + for ( const SOrderUnitPair & pair: m_OrderUnitPairs ) + { + if(pair.unit.GetPos() == unit.GetPos() ) + return 3; + } + + // Add the unit with a blank order + m_OrderUnitPairs.push_back( SOrderUnitPair(std::move(unit), SOrder()) ); + + + return 0; +} + +// Add a units, nonzero return value indicates error +int CTTRTSGame::AddUnits( CUnitVector&& units ) +{ + CUnitVector::iterator it; + + for ( it = units.begin(); it != units.end(); it++ ) + { + // Attempt the unit add + if ( AddUnit( std::move(*it) ) ) + return 1; + } + + // All units added successfully + return 0; +} + +// Verify any order +int CTTRTSGame::VerifyOrder( player_t player, const SOrder & order ) const +{ + int ret = 1; + + // Grab the unit ID + const unit_id_t unitID = order.unit; + + // Attempt to find the unit + for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) + { + // Accept if we have the unit + if (pair.unit.GetID() == unitID + && pair.unit.GetPlayer() == player) + { + ret = 0; + break; + } + } + + // for now, as long as the unit exists we can attempt the order + return ret; +} + +// Get unit by unit ID +const CUnit& CTTRTSGame::GetUnitByIDConst( unit_id_t id ) const +{ + for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) + { + // Attempt the unit add + if (pair.unit.GetID() == id ) + return pair.unit; + } + + // Return an invalid unit + static CUnit invalid_unit; + return invalid_unit; +} + +// Get an order by unit ID +const SOrder & CTTRTSGame::GetOrderByIDConst( unit_id_t id ) const +{ + for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) + { + // Attempt the unit add + if (pair.unit.GetID() == id ) + return pair.order; + } + + // Return an invalid unit + static SOrder invalid_order; + return invalid_order; +} + +// Get unit by unit ID +CUnit& CTTRTSGame::GetUnitByID( unit_id_t id ) +{ + for ( SOrderUnitPair & pair : m_OrderUnitPairs ) + { + // Attempt the unit add + if (pair.unit.GetID() == id ) + return pair.unit; + } + + // Return an invalid unit + static CUnit invalid_unit; + return invalid_unit; +} + +// Get a vector of the players in the current game +std::vector CTTRTSGame::GetPlayers() const +{ + std::vector players; + players.reserve(GetNumUnits()); + + // Grab all players + for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) + { + players.push_back(pair.unit.GetPlayer()); + } + + // Remove dupes + std::sort( players.begin(), players.end() ); + players.erase( std::unique( players.begin(), players.end() ), players.end() ); + + return players; +} + +// Check if we have a win state +player_t CTTRTSGame::GetWinningPlayer() const +{ + // Array of units for each Player + unsigned int units[(int) player_t::NUM_INVALID]; + memset(units,0,sizeof(units)); + + // Count up all the units for each Player + for ( const SOrderUnitPair & pair : m_OrderUnitPairs ) + { + const int player = (int) pair.unit.GetPlayer(); + units[player] += 1; + } + + // Default winning Player to invalid (no win) + player_t winningPlayer = player_t::NUM_INVALID; + + // For each of the players + for ( unsigned int i = 0; i < _countof(units); i++ ) + { + // if there are still units in this Player, and the winning Player hasn't been set + if( units[i] > 0 && winningPlayer == player_t::NUM_INVALID ) + { + winningPlayer = (player_t)i; + } + // Otherwise, if there are units in this Player and the winning Player HAS been set + else if ( units[i] > 0 ) + { + // Set back to invalid and break out of the loop + winningPlayer = player_t::NUM_INVALID; + break; + } + } + + return winningPlayer; +} + +// Check if any of the units can move +bool CTTRTSGame::UnitsCanMove() const +{ + for( const SOrderUnitPair& pair: m_OrderUnitPairs ) + { + uvector2 pos = pair.unit.GetPos(); + + // Assume if unit is adjacent to any valid tile, then it can move there + if( VerifyPosIsValidMovement(pos + vector2(1, 0) ) == 0 + || VerifyPosIsValidMovement(pos + vector2(0, 1)) == 0 + || VerifyPosIsValidMovement(pos + vector2(-1, 0)) == 0 + || VerifyPosIsValidMovement(pos + vector2(0, -1)) == 0 ) + { + return true; + } + } + + return false; +} + +// Check if the game is over +bool CTTRTSGame::GameOver() const +{ + return ( (GetWinningPlayer() != player_t::NUM_INVALID ) // We have a winning player + || GetNumUnits() == 0 + || !UnitsCanMove() ); // OR we have no units +} \ No newline at end of file diff --git a/source/ttrts/game.h b/source/ttrts/game.h new file mode 100644 index 0000000..1401b33 --- /dev/null +++ b/source/ttrts/game.h @@ -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 GetPlayers() const; + + // Get the vector of wall positions + inline std::vector 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 m_walls; // Vector of wall positions +}; + + +#endif //_GAME_H_ diff --git a/source/ttrts/gametypes.h b/source/ttrts/gametypes.h new file mode 100644 index 0000000..b01318d --- /dev/null +++ b/source/ttrts/gametypes.h @@ -0,0 +1,45 @@ +#ifndef _GAME_TYPES_H_ +#define _GAME_TYPES_H_ + +#include // 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::max(); + +// Typedef for unit visual representations +typedef char unitvis_c; +static const unitvis_c unitvis_invalid = std::numeric_limits::max(); + +// Coordinate types +typedef short coord_t; +static const coord_t coord_invalid = std::numeric_limits::max(); + +typedef unsigned short ucoord_t; +static const ucoord_t ucoord_invalid = std::numeric_limits::max(); + +// Direction representation +enum class dir_c : char +{ + N = 'N', + S = 'S', + E = 'E', + W = 'W' +}; + +// Helper function for count of an array +template +constexpr size_t _countof(T (&)[N]) { return N; } + +#endif //_GAME_TYPES_H_ \ No newline at end of file diff --git a/source/ttrts/order.h b/source/ttrts/order.h new file mode 100644 index 0000000..1ad995a --- /dev/null +++ b/source/ttrts/order.h @@ -0,0 +1,50 @@ +#ifndef _ORDERS_H_ +#define _ORDERS_H_ + +#include +#include + +#include "gametypes.h" + +#define ORDER_FORMATTER "ORDER:%c id:%u" + +// Type for all orders ( as a char ) +enum class command_c : char +{ + F = 'F', // Move forward one square + L = 'L', // Turn left + R = 'R', // Turn right + A = 'A', // Attack forwards until a unit or edge of the arena is hit + NUM_INVALID +}; + +// Container for an order +struct SOrder +{ + // Base constructor makes invalid order + SOrder() + : unit ( unit_id_invalid ) + , command( command_c::NUM_INVALID ) + {} + + // Unit order is for + unit_id_t unit; + + // Order command issued + command_c command; + + // Basic operators + inline bool operator==( const SOrder & rhs ) const; + inline bool operator!=( const SOrder & rhs ) const { return !(*this==rhs); } +}; + +// Simple == operator +inline bool SOrder::operator== ( const SOrder & rhs ) const +{ + return ( unit == rhs.unit ) && ( command == rhs.command); +} + +// Typedef a vector of orders +typedef std::vector COrderVector; + +#endif //_ORDERS_H_ diff --git a/source/ttrts/orderunitpair.h b/source/ttrts/orderunitpair.h new file mode 100644 index 0000000..278608c --- /dev/null +++ b/source/ttrts/orderunitpair.h @@ -0,0 +1,39 @@ +#ifndef _TTRTS_ORDERUNITPAIR_H_ +#define _TTRTS_ORDERUNITPAIR_H_ + +#include "order.h" +#include "unit.h" + +#include + +// 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 OrderUnitPairVector; + +#endif // _TTRTS_ORDERUNITPAIR_H_ \ No newline at end of file diff --git a/source/ttrts/unit.cpp b/source/ttrts/unit.cpp new file mode 100644 index 0000000..b6708c5 --- /dev/null +++ b/source/ttrts/unit.cpp @@ -0,0 +1,217 @@ +#include +#include "unit.h" + +#include // 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_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; +} diff --git a/source/ttrts/unit.h b/source/ttrts/unit.h new file mode 100644 index 0000000..7f6e77e --- /dev/null +++ b/source/ttrts/unit.h @@ -0,0 +1,94 @@ +#ifndef _UNIT_H_ +#define _UNIT_H_ + +#include +#include + +#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_ diff --git a/source/ttrts/vector2.h b/source/ttrts/vector2.h new file mode 100644 index 0000000..1cd2e34 --- /dev/null +++ b/source/ttrts/vector2.h @@ -0,0 +1,84 @@ +#ifndef _VECTOR2_H_ +#define _VECTOR2_H_ + +#include "gametypes.h" + +struct uvector2; + +struct vector2 +{ + vector2() : x (0), y (0) {} + vector2( coord_t _x, coord_t _y ) + : x(_x) + , y(_y) + {} + + coord_t x; + coord_t y; + + inline vector2 operator-() const { return { (coord_t)-x, (coord_t)-y }; } + + inline operator uvector2() const; + + // vec + inline vector2 operator+(const vector2& rhs) const { return { (coord_t)(rhs.x + x) , (coord_t)(rhs.y + y) }; } + inline vector2 operator-(const vector2& rhs) const { return *this + (-rhs); } + + inline const vector2& operator+=(const vector2& rhs) { *this = *this+rhs; return *this; } + inline const vector2& operator-=(const vector2& rhs) { return *this+=(-rhs); } + + inline bool operator==(const vector2& rhs) const { return ( rhs.x == x) && ( rhs.y == y); } + inline bool operator!=(const vector2& rhs) const { return !(*this==rhs); } +}; + +struct uvector2 +{ + uvector2() : x (0), y (0) {} + uvector2( ucoord_t _x, ucoord_t _y ) + : x(_x) + , y(_y) + {} + + ucoord_t x; + ucoord_t y; + + // Implicit conversion to vector 2 if needed + inline operator vector2() const { return { (coord_t)x, (coord_t)y }; } + + inline uvector2 operator-() const { return { (ucoord_t)-x, (ucoord_t)-y }; } + + // uvec + inline uvector2 operator+(const uvector2& rhs) const { return { (ucoord_t)(rhs.x + x) , (ucoord_t)(rhs.y + y) }; } + inline uvector2 operator-(const uvector2& rhs) const { return *this + (-rhs); } + + inline const uvector2& operator+=(const uvector2& rhs) { *this = *this+rhs; return *this; } + inline const uvector2& operator-=(const uvector2& rhs) { return *this+=(-rhs); } + + inline bool operator==(const uvector2& rhs) const { return ( rhs.x == x) && ( rhs.y == y); } + inline bool operator!=(const uvector2& rhs) const { return !(*this==rhs); } +}; + +inline vector2::operator uvector2() const { return { (ucoord_t)x, (ucoord_t)y }; } + +inline vector2 vecFromDir( dir_c dir ) +{ + switch( dir ) + { + case dir_c::N: + return { 0,1 }; + + case dir_c::E: + return { 1,0 }; + + case dir_c::S: + return { 0,-1 }; + + case dir_c::W: + return { -1,0 }; + + default: + return { 0,0 }; + } +} + +#endif //_VECTOR2_H_ diff --git a/stylesheets/core.css b/stylesheets/core.css deleted file mode 100644 index 7aaec46..0000000 --- a/stylesheets/core.css +++ /dev/null @@ -1,3 +0,0 @@ -@import url("screen.css"); -@import url("non-screen.css") handheld; -@import url("non-screen.css") only screen and (max-device-width:640px); \ No newline at end of file diff --git a/stylesheets/mobile.css b/stylesheets/mobile.css deleted file mode 100644 index c860c09..0000000 --- a/stylesheets/mobile.css +++ /dev/null @@ -1,510 +0,0 @@ -/* Generated by Font Squirrel (http://www.fontsquirrel.com) on February 9, 2012 */ - - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-regular-webfont.eot'); - src: url('../fonts/opensans-regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-regular-webfont.woff') format('woff'), - url('../fonts/opensans-regular-webfont.ttf') format('truetype'), - url('../fonts/opensans-regular-webfont.svg#OpenSansRegular') format('svg'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-italic-webfont.eot'); - src: url('../fonts/opensans-italic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-italic-webfont.woff') format('woff'), - url('../fonts/opensans-italic-webfont.ttf') format('truetype'), - url('../fonts/opensans-italic-webfont.svg#OpenSansItalic') format('svg'); - font-weight: normal; - font-style: italic; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-bold-webfont.eot'); - src: url('../fonts/opensans-bold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-bold-webfont.woff') format('woff'), - url('../fonts/opensans-bold-webfont.ttf') format('truetype'), - url('../fonts/opensans-bold-webfont.svg#OpenSansBold') format('svg'); - font-weight: bold; - font-style: normal; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-bolditalic-webfont.eot'); - src: url('../fonts/opensans-bolditalic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-bolditalic-webfont.woff') format('woff'), - url('../fonts/opensans-bolditalic-webfont.ttf') format('truetype'), - url('../fonts/opensans-bolditalic-webfont.svg#OpenSansBoldItalic') format('svg'); - font-weight: bold; - font-style: italic; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-extrabold-webfont.eot'); - src: url('../fonts/opensans-extrabold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-extrabold-webfont.woff') format('woff'), - url('../fonts/opensans-extrabold-webfont.ttf') format('truetype'), - url('../fonts/opensans-extrabold-webfont.svg#OpenSansExtrabold') format('svg'); - font-weight: bolder; - font-style: normal; -} - - -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} - -header, footer, section { - display: block; - position: relative; -} - -/* STYLES */ - -div.shell { - display: block; - width: 640px; - margin: 0 auto; -} - -a#forkme_banner { - display: none; -} - -/* header */ - -header { - position: relative; - z-index: 2; - margin: 0; - max-width: 640px; - top: 51px; -} - -header span.ribbon-inner { - position: relative; - display: block; - background-color: #cd596b; - border: 8px solid #7c334f; - padding: 6px; - z-index: 1; -} - -header span.left-tail, header span.right-tail { - position: relative; - display: block; - width: 19px; - height: 10px; - background: transparent url(../images/ribbon-tail-sprite-2x.png) 0 0 no-repeat; - position: absolute; - bottom: -10px; - z-index: 0; -} - -header span.left-tail { - background-position: 0 0; - left: 0; -} - -header span.right-tail { - background-position: -19px 0; - right: 0; -} - -header h1 { - background-color: #7c334f; - font-size: 2em; - font-weight: bolder; - font-style: normal; - text-transform: uppercase; - color: #ece4d8; - text-align: center; - line-height:1; - padding: 14px 20px 0; -} - -header h2 { - background-color: #7c334f; - font: bold italic .85em/1.5 Georgia, Times, “Times New Roman”, serif; - color: #e69b95; - padding-bottom: 14px; - margin-top: -3px; - text-align: center; -} - -section#downloads { - position: relative; - display: block; - height: 171px; - width: 602px; - padding-bottom: 150px; - margin: 51px auto -250px; - z-index: 1; - background: transparent url(../images/shield.png) center 0 no-repeat; -} - -section#downloads a { - display: none; -} - -span.banner-fix { - background: transparent url(../images/shield-fallback.png) center top no-repeat; - display: block; - height: 31px; - position: absolute; - width: 640px; - top: 20px; - -} - -section#main_content { - z-index: 2; - padding: 20px 40px 0; - min-height:185px; -} - -/* footer */ - -footer { - background: none; - padding-top: 104px; - margin: -94px auto 40px; - max-width:640px; - text-align: center; -} - -footer span.ribbon-outer { - display: block; - position: relative; - border-bottom: 2px solid #bdb6ad; -} - -footer span.ribbon-inner { - position: relative; - display: block; - background-color: #cd596b; - border: 8px solid #7c334f; - padding: 6px; - z-index: 1; -} - -footer p { - font-family: 'Open Sans', sans-serif; - font-weight: bold; - font-size: .6em; - color: #8b786f; -} - -footer a { - color: #cd596b; -} - -footer span.ribbon-inner p { - background-color: #7c334f; - margin: 0; - color: #e69b95; - font: bold italic 22px/1 Georgia, Times, “Times New Roman”, serif; - height: auto; - line-height: 1.1; - padding: 20px 0px 10px; -} - -footer span.ribbon-inner a { - display: block; - position: relative; - bottom: 0; - color: #7eb0d2; - font-family: 'Open Sans', sans-serif; - text-transform: uppercase; - font-style: normal; - font-weight: bolder; - font-size: 38px; - padding-bottom: 10px; -} - -footer span.ribbon-inner a:hover { - color: #7eb0d2; -} - -footer span.left-tail, footer span.right-tail { - position: relative; - display: block; - width: 23px; - height: 126px; - background: transparent url(../images/small-ribbon-tail-sprite-2x.png) 0 0 no-repeat; - position: absolute; - top: -126px; - z-index: 0; -} - -footer span.left-tail { - background-position: 0 0; - left: 0; -} - -footer span.right-tail { - background-position: -23px 0; - right: 0; -} - -footer span.octocat { - background: transparent url(../images/octocat-2x.png) 0 0 no-repeat; - display: block; - width: 60px; - height: 60px; - margin: 20px auto 0;} - -/* content */ - -body { - background: #ece4d8; - font: normal normal 30px/1.5 Georgia, Palatino,” Palatino Linotype”, Times, “Times New Roman”, serif; - color: #544943; - -webkit-font-smoothing: antialiased; -} - -a, a:hover { - color: #417090; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -h1,h2,h3,h4,h5,h6 { - font-family: 'Open Sans', sans-serif; - font-weight: bold; -} - -p { - margin: .7em 0 0; -} - -strong { - font-weight: bold; -} - -em { - font-style: italic; -} - -ol { - margin: .7em 0; - list-style-type: decimal; - padding-left: 1.35em; -} - -ul { - margin: .7em 0; - padding-left: 1.35em; -} - -ul li { - padding-left: 20px; - background: transparent url(../images/chevron-2x.png) left 15px no-repeat; -} - -blockquote { - font-family: 'Open Sans', sans-serif; - margin: 20px 0; - color: #8b786f; - padding-left: 1.35em; - background: transparent url('../images/blockquote-gfx-2x.png') 0 8px no-repeat; -} - -img { - -webkit-box-shadow: 0px 4px 0px #bdb6ad; - -moz-box-shadow: 0px 4px 0px #bdb6ad; - box-shadow: 0px 4px 0px #bdb6ad; - border: 4px solid #fff6e9; - max-width: 556px; -} - -hr { - border: none; - outline: none; - height: 42px; - background: transparent url('../images/hr-2x.jpg') center center repeat-x; - margin: 0 0 20px; -} - -code { - background: #fff6e9; - font: normal normal .9em/1.7 "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace; - padding: 0 5px 1px; -} - -pre { - margin: 10px 0 20px; - padding: .7em; - background: #fff6e9; - border-bottom: 4px solid #bdb6ad; - font: normal normal .9em/1.7 "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace; - overflow: auto; -} - -table { - background: #fff6e9; - display: table; - width: 100%; - border-collapse: separate; - border-bottom: 4px solid #bdb6ad; - margin: 10px 0; -} - -tr { - display: table-row; -} - -th { - display: table-cell; - padding: 2px 10px; - border: solid #ece4d8; - border-width: 0 4px 4px 0; - color: #cd596b; - font-family: 'Open Sans', sans-serif; - font-weight: bold; - font-size: .85em; -} - -td { - display: table-cell; - padding: 0 .7em; - border: solid #ece4d8; - border-width: 0 4px 4px 0; -} - -td:last-child, th:last-child { - border-right: none; -} - -tr:last-child td { - border-bottom: none; -} - -dl { - margin: .7em 0 20px; -} - -dt { - font-family: 'Open Sans', sans-serif; - font-weight: bold; -} - -dd { - padding-left: 1.35em; -} - -dd p:first-child { - margin-top: 0; -} - -/* Content based headers */ - -#main_content > .header-level-1:first-child, -#main_content > .header-level-2:first-child, -#main_content > .header-level-3:first-child, -#main_content > .header-level-4:first-child, -#main_content > .header-level-5:first-child, -#main_content > .header-level-6:first-child { - margin-top: 0; -} - -.header-level-1 { - font-size: 1.85em; - border-bottom: .2em double #d3ccc1; - color: #7c334f; - text-align: center; - font-style: italic; - margin: 1.1em 0 .38em; - line-height: 1.2; - padding-bottom: 10px -} - -.header-level-2 { - font-size: 1.58em; - color: #7c334f; - margin: .95em 0 .5em; - border-bottom: .1em solid #D3CCC1; - line-height: 1.2; - padding-bottom: 10px -} - -.header-level-3 { - margin: 20px 0 10px; - font-size: 1.45em; -} - -.header-level-4 { - margin: .6em 0; - font-size: 1.2em; - color: #cd596b; -} - -.header-level-5 { - margin: .7em 0; - font-size: 1em; - color: #8b786f; -} - -.header-level-6 { - margin: .8em 0; - font-size: .85em; - font-style: italic; -} \ No newline at end of file diff --git a/stylesheets/non-screen.css b/stylesheets/non-screen.css deleted file mode 100644 index eea5ecd..0000000 --- a/stylesheets/non-screen.css +++ /dev/null @@ -1,154 +0,0 @@ -a#forkme_banner { - display: none; -} - -div.shell { - width: 640px; -} - - -header { - max-width:640px; - margin: 0; - top: 51px; -} - -header span.ribbon-inner { - border: 8px solid #7c334f; - padding: 6px; -} - -header span.left-tail, header span.right-tail { - width: 19px; - height: 10px; - background: transparent url(../images/ribbon-tail-sprite-2x.png) 0 0 no-repeat; - bottom: -10px; -} - -header span.left-tail { - left: 0; -} - -header span.right-tail { - background-position: -19px 0; - right: 0; -} - -header h1 { - font-size: 2em; -} - -section#downloads { - height: 171px; - width: 602px; - margin: 51px auto -250px; - background: transparent url(../images/shield.png) center 0 no-repeat; -} - -section#downloads a { - display: none; -} - -span.banner-fix { - background: transparent url(../images/shield-fallback.png) center top no-repeat; - height: 31px; - width: 640px; - top: 20px; -} - -section#main_content { - padding: 20px 40px 0; -} - -footer { - max-width:640px; - background: none; -} - -footer span.left-tail, footer span.right-tail { - width: 23px; - height: 126px; - background: transparent url(../images/small-ribbon-tail-sprite-2x.png) 0 0 no-repeat; - top: -126px; -} - -footer span.left-tail { - left: 0; -} - -footer span.right-tail { - background-position: -23px 0; - right: 0; -} - -footer p { - font-size: .6em; -} - -footer span.ribbon-inner { - border: 8px solid #7c334f; - padding: 6px; -} - -footer span.ribbon-inner p { - font-size: 22px; - height: auto; - line-height: 1.1; - padding: 20px 0px 10px; -} - -footer span.ribbon-inner a { - font-size: 38px; - display: block; - bottom: 0; - padding-bottom: 10px; -} - -footer span.octocat { - background: transparent url(../images/octocat-2x.png) 0 0 no-repeat; - width: 60px; - height: 60px; - margin: 20px auto 0; -} - -body { - font: normal normal 30px/1.5 Georgia, Palatino,” Palatino Linotype”, Times, “Times New Roman”, serif; -} - -ul li { - padding-left: 20px; - background: transparent url(../images/chevron-2x.png) left 15px no-repeat; -} - -table { - border-bottom: 4px solid #bdb6ad; -} - -th { - border-width: 0 4px 4px 0; -} - -td { - border-width: 0 4px 4px 0; -} - -pre { - border-bottom: 4px solid #bdb6ad; -} - -img { - -webkit-box-shadow: 0px 4px 0px #bdb6ad; - -moz-box-shadow: 0px 4px 0px #bdb6ad; - box-shadow: 0px 4px 0px #bdb6ad; - border: 4px solid #fff6e9; - max-width: 556px; -} - -blockquote { - background: transparent url('../images/blockquote-gfx-2x.png') 0 8px no-repeat; -} - -hr { - height: 42px; - background: transparent url('../images/hr-2x.jpg') center center repeat-x; -} \ No newline at end of file diff --git a/stylesheets/print.css b/stylesheets/print.css deleted file mode 100644 index 32d9a9d..0000000 --- a/stylesheets/print.css +++ /dev/null @@ -1,34 +0,0 @@ -* { - background: none !important; - color: #333 !important; -} - -h1,h2,h3,h4,h5,h6 { - color: #7c334f !important; -} - -a { - color: #417090 !important; -} - -#main_content > .header-level-1:first-child, -#main_content > .header-level-2:first-child, -#main_content > .header-level-3:first-child, -#main_content > .header-level-4:first-child, -#main_content > .header-level-5:first-child, -#main_content > .header-level-6:first-child { - margin-top: 10px !important; -} - -#forkme_banner, -#downloads, -.left-tail, -.right-tail -{ -display: none !important; - -} - -.ribbon-inner,.ribbon-outer { - border: 0 !important; -} diff --git a/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css deleted file mode 100644 index c6a6452..0000000 --- a/stylesheets/pygment_trac.css +++ /dev/null @@ -1,69 +0,0 @@ -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ -.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ -.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #999999 } /* Generic.Heading */ -.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ -.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #d14 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #0086B3 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.highlight .ni { color: #800080 } /* Name.Entity */ -.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ -.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ -.highlight .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #d14 } /* Literal.String.Backtick */ -.highlight .sc { color: #d14 } /* Literal.String.Char */ -.highlight .sd { color: #d14 } /* Literal.String.Doc */ -.highlight .s2 { color: #d14 } /* Literal.String.Double */ -.highlight .se { color: #d14 } /* Literal.String.Escape */ -.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ -.highlight .si { color: #d14 } /* Literal.String.Interpol */ -.highlight .sx { color: #d14 } /* Literal.String.Other */ -.highlight .sr { color: #009926 } /* Literal.String.Regex */ -.highlight .s1 { color: #d14 } /* Literal.String.Single */ -.highlight .ss { color: #990073 } /* Literal.String.Symbol */ -.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #008080 } /* Name.Variable.Class */ -.highlight .vg { color: #008080 } /* Name.Variable.Global */ -.highlight .vi { color: #008080 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ - -.type-csharp .highlight .k { color: #0000FF } -.type-csharp .highlight .kt { color: #0000FF } -.type-csharp .highlight .nf { color: #000000; font-weight: normal } -.type-csharp .highlight .nc { color: #2B91AF } -.type-csharp .highlight .nn { color: #000000 } -.type-csharp .highlight .s { color: #A31515 } -.type-csharp .highlight .sc { color: #A31515 } diff --git a/stylesheets/screen.css b/stylesheets/screen.css deleted file mode 100644 index ba63020..0000000 --- a/stylesheets/screen.css +++ /dev/null @@ -1,569 +0,0 @@ -/* Generated by Font Squirrel (http://www.fontsquirrel.com) on February 9, 2012 */ - - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-regular-webfont.eot'); - src: url('../fonts/opensans-regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-regular-webfont.woff') format('woff'), - url('../fonts/opensans-regular-webfont.ttf') format('truetype'), - url('../fonts/opensans-regular-webfont.svg#OpenSansRegular') format('svg'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-italic-webfont.eot'); - src: url('../fonts/opensans-italic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-italic-webfont.woff') format('woff'), - url('../fonts/opensans-italic-webfont.ttf') format('truetype'), - url('../fonts/opensans-italic-webfont.svg#OpenSansItalic') format('svg'); - font-weight: normal; - font-style: italic; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-bold-webfont.eot'); - src: url('../fonts/opensans-bold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-bold-webfont.woff') format('woff'), - url('../fonts/opensans-bold-webfont.ttf') format('truetype'), - url('../fonts/opensans-bold-webfont.svg#OpenSansBold') format('svg'); - font-weight: bold; - font-style: normal; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-bolditalic-webfont.eot'); - src: url('../fonts/opensans-bolditalic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-bolditalic-webfont.woff') format('woff'), - url('../fonts/opensans-bolditalic-webfont.ttf') format('truetype'), - url('../fonts/opensans-bolditalic-webfont.svg#OpenSansBoldItalic') format('svg'); - font-weight: bold; - font-style: italic; -} - -@font-face { - font-family: 'Open Sans'; - src: url('../fonts/opensans-extrabold-webfont.eot'); - src: url('../fonts/opensans-extrabold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/opensans-extrabold-webfont.woff') format('woff'), - url('../fonts/opensans-extrabold-webfont.ttf') format('truetype'), - url('../fonts/opensans-extrabold-webfont.svg#OpenSansExtrabold') format('svg'); - font-weight: 800; - font-style: normal; -} - -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} - -header, footer, section { - display: block; - position: relative; -} - -/* STYLES */ - -div.shell { - display: block; - width: 670px; - margin: 0 auto; -} - -a#forkme_banner { - position: absolute; - top: 0; - left: 0; - width: 138px; - height: 138px; - display: block; - background: transparent url(../images/fork-sprite.png) 0 0 no-repeat; - text-indent: -9000px; - z-index: 3; -} - -a#forkme_banner:hover { - background-position: 0 -138px; -} - -/* header */ - -header { - position: relative; - z-index: 2; - margin: 0 auto; - max-width: 600px; - top: 38px; -} - -header span.ribbon-inner { - position: relative; - display: block; - background-color: #cd596b; - border: 4px solid #7c334f; - padding: 2px; - z-index: 1; -} - -header span.left-tail, header span.right-tail { - position: relative; - display: block; - width: 56px; - height: 105px; - background: transparent url(../images/ribbon-tail-sprite.png) 0 0 no-repeat; - position: absolute; - bottom: -37px; - z-index: 0; -} - -header span.left-tail { - background-position: 0 0; - left: -31px; -} - -header span.right-tail { - background-position: -56px 0; - right: -31px; -} - -header h1 { - background-color: #7c334f; - font-size: 2.5em; - font-weight: 800; - font-style: normal; - text-transform: uppercase; - color: #ece4d8; - text-align: center; - line-height:1; - padding: 14px 20px 0; -} - -header h2 { - background-color: #7c334f; - font: bold italic .85em/1.5 Georgia, Times, “Times New Roman”, serif; - color: #e69b95; - padding-bottom: 14px; - margin-top: -3px; - text-align: center; -} - -section#downloads, -div#no-downloads { - position: relative; - display: block; - height: 197px; - width: 550px; - padding-bottom: 150px; - margin: -80px auto -150px; - z-index: 1; - background: transparent url(../images/bg-ramp.jpg) center 171px no-repeat; -} - -div#no-downloads span.inner { - display: block; - position: relative; - height: 197px; - width: 550px; - background: transparent url(../images/download-sprite.png) 0 0 no-repeat; -} - -section#downloads a { - display: block; - position: relative; - height: 67px; - width: 275px; - padding-top: 130px; - background: transparent url(../images/download-sprite.png) 0 0 no-repeat; - text-align: center; - line-height: 1; - color: #fff; - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - font-weight: 800; - font-size: 1.3em; -} - -section#downloads a:hover { - text-decoration: none; -} - -section#downloads a em { - font: bold italic 12px/1 Georgia, Times, “Times New Roman”, serif; - color: #83b7da; - display: block; -} - -section#downloads a.zip { - float: left; - background-position: 0 0; -} - -section#downloads a.tgz { - float: right; - background-position: -275px 0; -} - -section#downloads a.zip:hover { - background-position: 0 -197px; -} - -section#downloads a.tgz:hover { - background-position: -275px -197px; -} - -span.banner-fix { - background: transparent url(../images/download-fallback-bg.png) center top no-repeat; - display: block; - height: 19px; - position: absolute; - width: 670px; - top: 19px; -} - -section#main_content { - z-index: 2; - padding: 20px 82px 0; - min-height:185px; -} - -/* footer */ - -footer { - background: transparent url(../images/footer-ramp.jpg) center -1px no-repeat; - padding-top: 104px; - margin: -94px auto 40px; - max-width: 560px; - text-align: center; -} - -footer span.ribbon-outer { - display: block; - position: relative; - border-bottom: 2px solid #bdb6ad; -} - -footer span.ribbon-inner { - position: relative; - display: block; - background-color: #cd596b; - border: 2px solid #7c334f; - padding: 1px; - z-index: 1; -} - -footer p { - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - font-weight: bold; - font-size: .8em; - color: #8b786f; -} - -footer a { - color: #cd596b; -} - -footer span.ribbon-inner p { - background-color: #7c334f; - margin: 0; - color: #e69b95; - font: bold italic 12px/1 Georgia, Times, “Times New Roman”, serif; - padding-bottom:4px; -} - -footer span.ribbon-inner a { - position: relative; - bottom: -1px; - color: #7eb0d2; - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - text-transform: uppercase; - font-style: normal; - font-weight: 800; - font-size: 1.2em; -} - -footer span.ribbon-inner a:hover { - color: #7eb0d2; -} - -footer span.left-tail, footer span.right-tail { - position: relative; - display: block; - width: 18px; - height: 29px; - background: transparent url(../images/small-ribbon-tail-sprite.png) 0 0 no-repeat; - position: absolute; - bottom: 5px; - z-index: 0; -} - -footer span.left-tail { - background-position: 0 0; - left: -11px; -} - -footer span.right-tail { - background-position: -18px 0; - right: -11px; -} - -footer span.octocat { - background: transparent url(../images/octocat.png) 0 0 no-repeat; - display: block; - width: 30px; - height: 30px; - margin: 0 auto; -} - -/* content */ - -body { - background: #ece4d8; - font: normal normal 15px/1.5 Georgia, Palatino,” Palatino Linotype”, Times, “Times New Roman”, serif; - color: #544943; - -webkit-font-smoothing: antialiased; -} - -a, a:hover { - color: #417090; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -h1,h2,h3,h4,h5,h6 { - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - font-weight: bold; -} - -p { - margin: .7em 0; -} - -strong { - font-weight: bold; -} - -em { - font-style: italic; -} - -ol { - margin: .7em 0; - list-style-type: decimal; - padding-left: 1.35em; -} - -ul { - margin: .7em 0; - padding-left: 1.35em; -} - -ul li { - padding-left: 10px; - background: transparent url(../images/chevron.png) left 6px no-repeat; -} - -blockquote { - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - margin: 20px 0; - color: #8b786f; - padding-left: 1.35em; - background: transparent url('../images/blockquote-gfx.png') 0 4px no-repeat; -} - -img { - -webkit-box-shadow: 0px 2px 0px #bdb6ad; - -moz-box-shadow: 0px 2px 0px #bdb6ad; - box-shadow: 0px 2px 0px #bdb6ad; - border: 2px solid #fff6e9; - max-width: 502px; -} - -hr { - border: none; - outline: none; - height: 18px; - background: transparent url('../images/hr.jpg') center center repeat-x; - margin: 0 0 20px; -} - -code { - background: #fff6e9; - font: normal normal .8em/1.7 "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace; - padding: 0 5px 1px; -} - -pre { - margin: 10px 0 20px; - padding: .7em; - background: #fff6e9; - border-bottom: 2px solid #bdb6ad; - font: normal normal .9em/1.7 "Lucida Sans Typewriter", "Lucida Console", Monaco, "Bitstream Vera Sans Mono", monospace; - overflow: auto; -} - -pre code { - padding: 0; -} - -table { - background: #fff6e9; - display: table; - width: 100%; - border-collapse: separate; - border-bottom: 2px solid #bdb6ad; - margin: 10px 0; -} - -tr { - display: table-row; -} - -th { - display: table-cell; - padding: 2px 10px; - border: solid #ece4d8; - border-width: 0 2px 2px 0; - color: #cd596b; - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - font-weight: bold; - font-size: .85em; -} - -td { - display: table-cell; - padding: 0 .7em; - border: solid #ece4d8; - border-width: 0 2px 2px 0; -} - -td:last-child, th:last-child { - border-right: none; -} - -tr:last-child td { - border-bottom: none; -} - -dl { - margin: .7em 0 20px; -} - -dt { - font-family: 'Open Sans', Myriad, Calibri, sans-serif; - font-weight: bold; -} - -dd { - padding-left: 1.35em; -} - -dd p:first-child { - margin-top: 0; -} - -/* Content based headers */ - -#main_content > .header-level-1:first-child, -#main_content > .header-level-2:first-child, -#main_content > .header-level-3:first-child, -#main_content > .header-level-4:first-child, -#main_content > .header-level-5:first-child, -#main_content > .header-level-6:first-child { - margin-top: 0; -} - -.header-level-1 { - font-size: 1.85em; - border-bottom: .2em double #d3ccc1; - color: #7c334f; - text-align: center; - font-style: italic; - margin: 1.1em 0 .38em; - line-height: 1.2; - padding-bottom: 10px -} - -.header-level-2 { - font-size: 1.58em; - color: #7c334f; - margin: .95em 0 .5em; - border-bottom: .1em solid #D3CCC1; - line-height: 1.2; - padding-bottom: 10px -} - -.header-level-3 { - margin: 20px 0 10px; - font-size: 1.45em; -} - -.header-level-4 { - margin: .6em 0; - font-size: 1.2em; - color: #cd596b; -} - -.header-level-5 { - margin: .7em 0; - font-size: 1em; - color: #8b786f; -} - -.header-level-6 { - margin: .8em 0; - font-size: .85em; - font-style: italic; -}