There are many ways this could be accomplished, depending on your goals, language selection, and development environment. If you’re familiar with software testing practices, I’d recommend using such techniques. I tend to use C# for most of my current development, so I’ll use that as an example.
I do my development in Visual Studio and use the MSTest framework to create unit tests for my AI. CG begins execution in static void Main()
and reads input from stdin
. I leave the game initialization input parsing in Main()
, but then immediately jump out of static context by creating an instance of my AI class. For example, my Main()
for Hypersonic looks like:
namespace HyperSonic {
public class Player {
public static int Width;
public static int Height;
public static int MyId;
public static void Main(string[] args) {
var inputs = Console.ReadLine().Split(' ');
Width = int.Parse(inputs[0]);
Height = int.Parse(inputs[1]);
MyId = int.Parse(inputs[2]);
var p = new Player();
while (true) {
var move = p.PlayOneRound();
Console.WriteLine(move);
}
}
}
}
Then, there’s per-round input parsing. This I accomplish via protected methods that can be overridden, using the unit testing concept of object seams. For example:
public string PlayOneRound() {
SetupGrid();
SetupEntities();
// logic goes here!
return reallyAwesomeMove;
}
private void SetupGrid() {
_startState = new State();
_startState.Grid = Enumerable.Range(0, Height)
.Select(_ => GetNextBoardLine().ToCharArray())
.ToArray();
}
protected virtual string GetNextBoardLine() {
return Console.ReadLine();
}
private void SetupEntities() {
var numEntities = GetNumEntities();
for (var i = 0; i < numEntities; i++) {
var inputs = GetNextEntity().Split(' ');
var entityType = int.Parse(inputs[0]);
var owner = int.Parse(inputs[1]);
var x = int.Parse(inputs[2]);
var y = int.Parse(inputs[3]);
var param1 = int.Parse(inputs[4]);
var param2 = int.Parse(inputs[5]);
}
}
protected virtual int GetNumEntities() {
return int.Parse(Console.ReadLine());
}
protected virtual string GetNextEntity() {
return Console.ReadLine();
}
From here, it’s very easy to create a test class that overrides these inputs:
namespace HyperSonic {
[TestClass]
public class PlayerTest {
[TestMethod]
public void ShouldComeUpWithReallyAwesomeMove() {
var p = new PlayerForTest(4, 4);
p.SetupForRound(new[] {
"....",
"....",
"....",
"...0"
}, new[] {
"0 0 0 0 1 3"
});
var move = p.PlayOneRound();
Assert.IsReallyAwesome(move);
}
private class PlayerForTest : Player {
private List<string> _gridLines;
private List<string> _entities;
public PlayerForTest(int width, int height) {
Width = width;
Height = height;
MyId = 0;
}
internal void SetupForRound(string[] grid, string[] entities) {
_gridLines = grid.ToList();
_entities = entities.ToList();
}
protected override int GetNumEntities() {
return _entities.Count;
}
protected override string GetNextEntity() {
var entity = _entities[0];
_entities.RemoveAt(0);
return entity;
}
protected override string GetNextBoardLine() {
var line = _gridLines[0];
_gridLines.RemoveAt(0);
return line;
}
}
}
}
Hopefully, it should be pretty obvious how you could use this formula to expand from single-move testing to a framework that can pit two different AIs (separate class instances) against each other.
Make sense?