Single-player mode is a cross-client effort to allow users to configure a local server and AI opponents automatically via an easy wizard interface. This was originally developed for Google Summer of Code 2008 by Aaron Mavrinac / ezod.
Single player mode is currently implemented in libtpclient-py, with a wizard front-end in tpclient-pywx.
The required XML descriptions are currently available for the following components:
- tpserver-cpp (>=0.6.0)
- daneel-ai
The SinglePlayerGame object in libtpclient-py provides all back end functionality:
from tp.client.SinglePlayer import SinglePlayerGame game = SinglePlayerGame()
The player first chooses a ruleset from the ruleset list:
print 'SELECT RULESET' while game.rname not in game.rulesets: game.rname = raw_input('Choose a ruleset from ' + str(game.rulesets) + ': ')
If there are multiple servers implementing the chosen ruleset, a server must be chosen:
slist = game.list_servers_with_ruleset() if len(slist) > 1: print 'SELECT SERVER' print 'There are multiple servers implementing the', game.rname, 'ruleset.' while game.sname not in slist: game.sname = raw_input('Choose a server from ' + str(slist) + ': ') else: game.sname = slist[0]
Ruleset options, if any, may be specified:
paramlist = game.list_rparams() if len(paramlist): print 'RULESET OPTIONS' for param in paramlist.keys(): game.rparams[param] = raw_input(paramlist[param]['longname'] + ' (' + paramlist[param]['type'] + '): ')
Likewise for server options:
paramlist = game.list_sparams() if len(paramlist): print 'SERVER OPTIONS' for param in paramlist.keys(): game.sparams[param] = raw_input(paramlist[param]['longname'] + ' (' + paramlist[param]['type'] + '): ')
Opponents may then be added:
ailist = game.list_aiclients_with_ruleset() if len(ailist): print 'ADD OPPONENTS' while len(ailist) > 0: aiuser = raw_input('Enter an opponent name (leave blank to stop adding opponents): ') if aiuser: ainame = '' while ainame not in ailist: ainame = raw_input('Please select an AI client from ' + str(ailist) + ': ') aiparams = {} paramlist = game.list_aiparams(ainame) for param in paramlist.keys(): aiparams[param] = raw_input(paramlist[param]['longname'] + ' (' + paramlist[param]['type'] + '): ') game.add_opponent(ainame, aiuser, aiparams) else: break
When everything is complete, start the game:
port = game.start() if port: # start player's client and connect to localhost:port
Finally, to clean up when the game is done, stop it:
game.stop()
In order for a component to be available for single-player mode, it must install an XML description of itself (using tpconfig.dtd) somewhere. On Unix platforms, libtpclient-py looks in /usr/share/tp, /usr/local/share/tp, /opt/tp, and ./singleplayer in its own install path. On Windows platforms, it looks at the registry key "HKLM\Software\Thousand Parsec\SinglePlayer" and adds any values it finds there to the list of search paths.
For development purposes, a Git checkout of libtpclient-py will also look at all other directories at the same level as its own repository. Special XML files can be included at the top level of the repository for this purpose. The type attribute of the tpconfig root tag must be set to inplace, and the name attribute of servers and AI clients is conventionally suffixed with -inplace and the longname attribute with (Development) as well to distinguish them from installed copies.
There are forced parameters (simply added to the command line as-is) and regular parameters (offered to the player in the wizard). Any options required for single-player mode (including those which allow for non-interactive control) should be forced parameters; any that are relevant to a single-player game and can be set by the player should be regular parameters.
There following parameter types are supported (for formatting and validation):
Type | XML Type | Format String | Appearance |
---|---|---|---|
Integer | I | %d | Numeric Textbox |
String | S | %s | Alpha Textbox |
File | F | %s | File Dialog |
Boolean | B | none | Checkbox |
For servers, the commandstring and/or forced parameters should include exactly one instance each of the following format strings, which will be supplied by the client:
- %(rname)s - the ruleset name
- %(port)d - the port to listen on
<?xml version="1.0"?> <!DOCTYPE tpconfig SYSTEM "http://thousandparsec.net/tp/downloads/tpconfig.dtd"> <tpconfig type="installed"> <server name="tpserver-foo"> <longname>tpserver-foo</longname> <version>0.1.2</version> <description>A new Thousand Parsec server.</description> <commandstring>tpserver-foo --ruleset %(rname)s --port %(port)d</commandstring> <forced>--some_option yes</forced> <forced>--some_other_option</forced> <parameter name="turnlength" type="I"> <longname>Turn Length</longname> <description>Maximum length of a turn (0 for unlimited).</description> <default>0</default> <commandstring>--turn_length %d</commandstring> </parameter> </server> </tpconfig>
Rulesets may have their own XML files, with the name attribute of the server tag set to the name of the server they are associated with, or may be written directly into the server's XML file.
<?xml version="1.0"?> <!DOCTYPE tpconfig SYSTEM "http://thousandparsec.net/tp/downloads/tpconfig.dtd"> <tpconfig type="installed"> <server name="tpserver-foo"> <ruleset name="bar"> <longname>Bar: Galactic Carnage</longname> <version>0.1</version> <description>A very violent ruleset for tpserver-foo.</description> <parameter name="map" type="F"> <longname>Map</longname> <description>Map file to load.</description> <default/> <commandstring>--bar_map %s</commandstring> </parameter> <parameter name="fleets" type="I"> <longname>Fleets</longname> <description>Starting fleet count.</description> <default>3</default> <commandstring>--bar_starting_fleets %d</commandstring> </parameter> </ruleset> </server> </tpconfig>
For AI clients, the commandstring and/or forced parameters should include exactly one instance each of the following format strings (unless marked optional), which will be supplied by the client:
- %(rname)s - the ruleset name (optional - for multi-ruleset AI clients)
- %(user)s - a username for the AI
- %(port)d - the port to connect to
<?xml version="1.0"?> <!DOCTYPE tpconfig SYSTEM "http://thousandparsec.net/tp/downloads/tpconfig.dtd"> <tpconfig type="installed"> <aiclient name="baz-ai"> <longname>baz-ai</longname> <version>0</version> <description>An invincible AI from another dimension.</description> <commandstring>baz-ai -r %(rname)s -u %(user)s -h localhost:%(port)d</commandstring> <rules>bar</rules> <rules>qux</rules> </aiclient> </tpconfig>