// Statistics.java
package net.sf.gogui.tools.statistics;
import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import net.sf.gogui.game.ConstGame;
import net.sf.gogui.game.ConstNode;
import net.sf.gogui.game.ConstGameInfo;
import net.sf.gogui.game.ConstGameTree;
import net.sf.gogui.game.Game;
import net.sf.gogui.game.NodeUtil;
import net.sf.gogui.go.GoColor;
import static net.sf.gogui.go.GoColor.BLACK;
import static net.sf.gogui.go.GoColor.EMPTY;
import net.sf.gogui.go.GoPoint;
import net.sf.gogui.go.InvalidPointException;
import net.sf.gogui.go.Move;
import net.sf.gogui.gtp.GtpClient;
import net.sf.gogui.gtp.GtpClientBase;
import net.sf.gogui.gtp.GtpError;
import net.sf.gogui.gtp.GtpSynchronizer;
import net.sf.gogui.sgf.SgfError;
import net.sf.gogui.sgf.SgfReader;
import net.sf.gogui.util.ErrorMessage;
import net.sf.gogui.util.Platform;
import net.sf.gogui.util.StringUtil;
import net.sf.gogui.util.Table;
/** Run commands of a GTP engine on all positions in a game collection. */
public class Statistics
{
public void run(String program, ArrayList<String> sgfFiles, int size,
ArrayList<String> commands,
ArrayList<String> beginCommands,
ArrayList<String> finalCommands, boolean verbose,
boolean allowSetup, boolean backward, boolean random)
throws ErrorMessage, GtpError, IOException
{
run(new GtpClient(program, null, verbose, null), program, sgfFiles,
size, commands, beginCommands, finalCommands, allowSetup,
backward, random);
}
/** Construct with existing GTP engine.
@param gtp The GTP engine
@param program Program command (null, if gtp is not an instance of
GtpClient)
@param sgfFiles List containing the SGF file names
@param size The board size used in the games (all games must have the
same board size)
@param commands List containing the commands to run in every position
@param beginCommands List containing the commands to run in the first
position
@param finalCommands List containing the commands to run in the last
position
@param allowSetup true, if setup stones in the games are allowed and
should not generate en error
@param backward true, if games should be iterated backwards (counting
the moves starting with one at the last move)
@param random true, if only one random position should be selected
from each game */
public void run(GtpClientBase gtp, String program,
ArrayList<String> sgfFiles, int size,
ArrayList<String> commands,
ArrayList<String> beginCommands,
ArrayList<String> finalCommands, boolean allowSetup,
boolean backward, boolean random)
throws ErrorMessage, IOException
{
new FileCheck(sgfFiles, size, allowSetup);
m_size = size;
m_allowSetup = allowSetup;
m_backward = backward;
m_random = random;
initCommands(commands, beginCommands, finalCommands);
ArrayList<String> columnHeaders = new ArrayList<String>();
columnHeaders.add("File");
columnHeaders.add("Move");
for (int i = 0; i < m_commands.size(); ++i)
columnHeaders.add(getCommand(i).m_columnTitle);
m_table = new Table(columnHeaders);
m_table.setProperty("Size", Integer.toString(size));
m_gtp = gtp;
m_synchronizer = new GtpSynchronizer(m_gtp);
m_gtp.queryProtocolVersion();
m_gtp.queryName();
if (program != null)
m_table.setProperty("Program", program);
m_table.setProperty("Name", m_gtp.getLabel());
m_table.setProperty("Version", m_gtp.queryVersion());
String host = Platform.getHostInfo();
m_table.setProperty("Host", host);
m_table.setProperty("Date", StringUtil.getDate());
for (int i = 0; i < sgfFiles.size(); ++i)
handleFile(sgfFiles.get(i));
m_gtp.send("quit");
m_gtp.close();
m_gtp.waitForExit();
m_table.setProperty("Games", Integer.toString(m_numberGames));
m_table.setProperty("Backward", backward ? "yes" : "no");
m_table.setProperty("Random", random ? "yes" : "no");
}
/** Set maximum move number for positions to run the commands on.
Default is Integer.MAX_VALUE. */
public void setMax(int max)
{
m_max = max;
}
/** Set minimum move number for positions to run the commands on.
Default is zero. */
public void setMin(int min)
{
m_min = min;
}
/** Don't write information about progress.
Default is false. */
public void setQuiet(boolean enable)
{
m_quiet = enable;
}
/** Save result table of last run. */
public void saveTable(File output) throws IOException
{
FileWriter writer = new FileWriter(output);
try
{
m_table.save(writer);
}
finally
{
writer.close();
}
}
private static class Command
{
public boolean m_begin;
public boolean m_final;
public String m_command;
public String m_columnTitle;
}
private boolean m_allowSetup;
private boolean m_backward;
private boolean m_random;
private boolean m_quiet;
private int m_max = Integer.MAX_VALUE;
private int m_min = 0;
private int m_numberGames;
private int m_size;
private double m_lastCpuTime = 0;
private GtpClientBase m_gtp;
private static final NumberFormat FORMAT1 = StringUtil.getNumberFormat(1);
private static final NumberFormat FORMAT2 = StringUtil.getNumberFormat(2);
private Table m_table;
private ArrayList<Command> m_commands;
private GtpSynchronizer m_synchronizer;
private void addCommand(String commandLine, boolean isBegin,
boolean isFinal) throws ErrorMessage
{
commandLine = commandLine.trim();
if (commandLine.equals(""))
throw new ErrorMessage("Empty command not allowed");
Command command = new Command();
command.m_command = commandLine;
command.m_begin = isBegin;
command.m_final = isFinal;
int numberSame = 0;
Command firstSame = null;
for (int i = 0; i < m_commands.size(); ++i)
if (getCommand(i).m_command.equals(commandLine))
{
firstSame = getCommand(i);
++numberSame;
}
if (numberSame == 0)
command.m_columnTitle = commandLine;
else
{
if (numberSame == 1)
firstSame.m_columnTitle = firstSame.m_columnTitle + " (1)";
command.m_columnTitle = commandLine + " ("
+ (numberSame + 1) + ")";
}
m_commands.add(command);
}
private void addCommands(ArrayList<String> commands, boolean isBegin,
boolean isFinal) throws ErrorMessage
{
for (String c : commands)
addCommand(c, isBegin, isFinal);
}
private void checkGame(ConstGameTree tree, String name) throws ErrorMessage
{
int size = tree.getBoardSize();
if (size != m_size)
throw new ErrorMessage(name + " has not size " + m_size);
ConstNode root = tree.getRootConst();
GoColor toMove = BLACK;
for (ConstNode node = root; node != null; node = node.getChildConst())
{
if (node.hasSetup())
{
if (m_allowSetup)
{
if (node == root)
toMove = EMPTY;
else
throw new ErrorMessage(name + " contains setup stones"
+ " in non-root position");
}
else
throw new ErrorMessage(name + " contains setup stones");
}
Move move = node.getMove();
if (move != null)
{
if (toMove == EMPTY)
toMove = move.getColor();
if (move.getColor() != toMove)
throw new ErrorMessage(name
+ "has non-alternating moves");
toMove = toMove.otherColor();
}
}
}
private String convertCommand(String command, GoColor toMove)
{
if (command.equals("reg_genmove"))
return command + ' ' + toMove;
return command;
}
private String convertResponse(String command, String response,
GoColor toMove, Move move) throws GtpError
{
if (command.equals("cputime"))
{
try
{
double cpuTime = Double.parseDouble(response);
double diff = cpuTime - m_lastCpuTime;
m_lastCpuTime = cpuTime;
return FORMAT2.format(diff);
}
catch (NumberFormatException e)
{
return response;
}
}
else if (command.equals("estimate_score"))
{
String arg[] = StringUtil.splitArguments(response);
if (arg.length == 0)
return response;
return convertScore(arg[0]);
}
else if (command.equals("final_score"))
{
return convertScore(response);
}
else if (command.equals("reg_genmove"))
{
if (move == null)
return "";
try
{
GoPoint point = GoPoint.parsePoint(response, m_size);
return Move.get(toMove, point) == move ? "1" : "0";
}
catch (InvalidPointException e)
{
throw new GtpError("Program sent invalid move: " + response);
}
}
return response;
}
/** Tries to convert score into number.
@return Score string or original string, if conversion fails. */
private String convertScore(String string)
{
String score = string.trim();
double sign = 1;
if (score.startsWith("W+"))
{
score = score.substring(2);
sign = -1;
}
else if (score.startsWith("B+"))
score = score.substring(2);
try
{
return FORMAT1.format(sign * Double.parseDouble(score));
}
catch (NumberFormatException e)
{
return string;
}
}
private void initCommands(ArrayList<String> commands,
ArrayList<String> beginCommands,
ArrayList<String> finalCommands)
throws ErrorMessage
{
m_commands = new ArrayList<Command>();
if (beginCommands != null)
addCommands(beginCommands, true, false);
if (commands != null)
addCommands(commands, false, false);
if (finalCommands != null)
addCommands(finalCommands, false, true);
if (m_commands.isEmpty())
throw new ErrorMessage("No commands defined");
}
private Command getCommand(int index)
{
return m_commands.get(index);
}
private void handleFile(String name)
throws ErrorMessage, FileNotFoundException, GtpError,
SgfError
{
File file = new File(name);
InputStream in = new FileInputStream(file);
SgfReader reader = new SgfReader(in, file, null, 0);
++m_numberGames;
Game game = new Game(reader.getTree());
checkGame(game.getTree(), name);
if (m_random)
iteratePositionsRandom(game, name);
else if (m_backward)
iteratePositionsBackward(game, name);
else
iteratePositions(game, name);
}
private void handlePosition(String name, GoColor toMove, Move move,
int number, boolean beginCommands,
boolean regularCommands,
boolean finalCommands)
throws GtpError
{
if (! m_quiet)
System.err.println(name + ":" + number);
m_table.startRow();
try
{
m_table.set("File", name);
m_table.set("Move", number);
for (int i = 0; i < m_commands.size(); ++i)
{
Command command = getCommand(i);
if (command.m_begin && beginCommands)
{
String response = send(command.m_command, toMove, move);
m_table.set(command.m_columnTitle, response);
}
}
for (int i = 0; i < m_commands.size(); ++i)
{
Command command = getCommand(i);
if (! command.m_begin && ! command.m_final && regularCommands)
{
String response = send(command.m_command, toMove, move);
m_table.set(command.m_columnTitle, response);
}
}
for (int i = 0; i < m_commands.size(); ++i)
{
Command command = getCommand(i);
if (command.m_final && finalCommands)
{
String response = send(command.m_command, toMove, move);
m_table.set(command.m_columnTitle, response);
}
}
}
catch (Table.InvalidLocation e)
{
System.err.println(e.getMessage());
// Table was created by this class in correct format
assert false;
}
}
private void iteratePositions(Game game, String name) throws GtpError
{
int number = 0;
for (ConstNode node = game.getRoot(); node != null;
node = node.getChildConst())
{
game.gotoNode(node);
synchronize(game);
Move move = node.getMove();
boolean beginCommands = ! node.hasFather();
boolean regularCommands =
((move != null || node.hasSetup() || ! node.hasFather())
&& number >= m_min && number <= m_max);
boolean finalCommands = ! node.hasChildren();
if (beginCommands || regularCommands || finalCommands)
handlePosition(name, node.getToMove(), move, number,
beginCommands, regularCommands, finalCommands);
++number;
}
}
private void iteratePositionsBackward(Game game, String name)
throws GtpError
{
int number = 0;
for (ConstNode node = NodeUtil.getLast(game.getRoot()); node != null;
node = node.getFatherConst())
{
game.gotoNode(node);
synchronize(game);
Move move = node.getMove();
boolean beginCommands = ! node.hasChildren();
boolean regularCommands =
((move != null || node.hasSetup() || ! node.hasFather())
&& number >= m_min && number <= m_max);
boolean finalCommands = ! node.hasFather();
if (beginCommands || regularCommands || finalCommands)
handlePosition(name, node.getToMove(), move, number,
beginCommands, regularCommands, finalCommands);
++number;
}
}
private void iteratePositionsRandom(Game game, String name)
throws GtpError
{
int minDepth;
int maxDepth;
if (m_backward)
{
int depth = NodeUtil.getDepth(NodeUtil.getLast(game.getRoot()));
minDepth = depth - m_max;
maxDepth = depth - m_min;
}
else
{
minDepth = m_min;
maxDepth = m_max;
}
ConstNode node = NodeUtil.selectRandom(game.getRoot(), minDepth,
maxDepth);
if (node == null)
return;
int number = NodeUtil.getDepth(node);
game.gotoNode(node);
synchronize(game);
Move move = node.getMove();
boolean beginCommands = ! node.hasChildren();
boolean regularCommands =
(move != null || node.hasSetup() || ! node.hasFather());
boolean finalCommands = ! node.hasFather();
if (beginCommands || regularCommands || finalCommands)
handlePosition(name, node.getToMove(), move, number,
beginCommands, regularCommands, finalCommands);
}
private String send(String command, GoColor toMove, Move move)
throws GtpError
{
String cmd = convertCommand(command, toMove);
String response = m_gtp.send(cmd).trim();
response = response.replaceAll("\t", " ");
response = response.replaceAll("\n", " ");
return convertResponse(command, response, toMove, move);
}
private void synchronize(ConstGame game) throws GtpError
{
ConstNode node = game.getGameInfoNode();
ConstGameInfo info = game.getGameInfo(node);
m_synchronizer.synchronize(game.getBoard(), info.getKomi(),
info.getTimeSettings());
}
}