// TextParser.java
package net.sf.gogui.text;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import net.sf.gogui.go.Board;
import net.sf.gogui.go.GoColor;
import static net.sf.gogui.go.GoColor.BLACK;
import static net.sf.gogui.go.GoColor.WHITE;
import net.sf.gogui.go.GoPoint;
import net.sf.gogui.go.PointList;
/** Parse Go positions from ASCII text.
Can handle a variety of formats. Black stones can be represented by 'X',
'x', '@' or '#'; white stones by 'O' or 'o' (however one representation
must be used consistently); '.' and '+' are interpreted as empty points.
Space characters are allowed between the points; leading numbers (or '|',
'>' and '$' characters) are ignored, as well as single inserted invalid
lines (to support appended text after the row that was wrapped).
Non-rectangular positions will be read into the smallest containing square
board size at the top left position. If a a line contains the string
"b|black|w|white to play|move" (case-insensitive), it will be used to set
the current player in the position. */
public class TextParser
{
public TextParser()
{
}
/** Get board with parsed position.
Only valid after calling parse. */
public Board getBoard()
{
return m_board;
}
public void parse(Reader reader) throws ParseError
{
try
{
m_reader = new BufferedReader(reader);
m_charBlack = null;
m_charWhite = null;
String line;
while (true)
{
line = readLine();
if (line == null)
throw new ParseError("could not find position");
if (isBoardRow(line, true))
break;
}
m_board = new Board(m_width);
checkToPlay(line);
parseBoardRow(line, m_board.getSize() - 1);
int i = 2;
while (true)
{
line = readLine();
if (line == null)
break;
checkToPlay(line);
if (! isBoardRow(line, false))
{
// Allow one failure if long lines were wrapped
line = readLine();
if (line != null)
checkToPlay(line);
}
if (line == null || ! isBoardRow(line, false))
break;
if (m_board.getSize() - i < 0)
increaseBoardSize();
parseBoardRow(line, m_board.getSize() - i);
++i;
}
if (i != m_board.getSize() + 1)
// Assume that non-square positions are anchored at lower-left
// corner
shiftBoardDown(m_board.getSize() + 1 - i);
}
finally
{
try
{
m_reader.close();
}
catch (IOException e)
{
}
m_reader = null;
}
}
private int m_width;
private Character m_charBlack;
private Character m_charWhite;
private Board m_board;
private BufferedReader m_reader;
private void checkToPlay(String line)
{
line = line.toLowerCase();
if (line.contains("black to play") || line.contains("b to play")
|| line.contains("black to move") || line.contains("b to move"))
m_board.setToMove(BLACK);
if (line.contains("white to play") || line.contains("w to play")
|| line.contains("white to move") || line.contains("w to move"))
m_board.setToMove(WHITE);
}
/** Ignore characters at beginning of line.
Ignores characters that are sometimes left of the actual position.
The included characters are digits, pipe symbols (edge) and dollar
signs. */
private int ignoreBeginning(String line)
{
int i;
for (i = 0; i < line.length(); ++i)
{
char c = line.charAt(i);
if (! Character.isSpaceChar(c) && ! Character.isDigit(c)
&& c != '$' && c != '|' && c != '>')
break;
}
return i;
}
/** Increase boardsize by one.
Keep existing position (shifted upward by one line) */
private void increaseBoardSize()
{
int newSize = m_board.getSize() + 1;
Board newBoard = new Board(newSize);
PointList black = new PointList();
PointList white = new PointList();
for (GoPoint p : m_board)
{
GoColor c = m_board.getColor(p);
p = p.up(newSize);
if (c == BLACK)
black.add(p);
else if (c == WHITE)
white.add(p);
}
newBoard.setup(black, white, m_board.getToMove());
m_board = newBoard;
}
private boolean isBlack(char c)
{
if (m_charBlack != null)
return (c == m_charBlack.charValue());
if (c == 'X' || c == '@' || c == '#' || c == 'x')
{
m_charBlack = c;
return true;
}
return false;
}
private static boolean isEmpty(char c)
{
return (c == '.' || c == '+');
}
private boolean isWhite(char c)
{
if (m_charWhite != null)
return (c == m_charWhite.charValue());
if (c == 'o' || c == 'O')
{
m_charWhite = c;
return true;
}
return false;
}
private boolean isBoardRow(String line, boolean initSize)
throws ParseError
{
int size = 0;
for (int i = ignoreBeginning(line); i < line.length(); ++i)
{
char c = line.charAt(i);
if (Character.isSpaceChar(c))
continue;
if (isBlack(c) || isWhite(c) || isEmpty(c))
++size;
else
break;
}
// Don't try to parse boards smaller than 3x3
if (size < 3 || size > GoPoint.MAX_SIZE)
return false;
if (initSize)
m_width = size;
return true;
}
private void parseBoardRow(String line, int y) throws ParseError
{
int x = 0;
for (int i = ignoreBeginning(line); i < line.length(); ++i)
{
char c = line.charAt(i);
if (Character.isSpaceChar(c))
continue;
if (isBlack(c))
{
if (x >= m_board.getSize())
increaseBoardSize();
PointList black =
new PointList(m_board.getSetup(BLACK));
PointList white =
new PointList(m_board.getSetup(WHITE));
black.add(GoPoint.get(x, y));
m_board.setup(black, white, m_board.getToMove());
++x;
}
else if (isWhite(c))
{
if (x >= m_board.getSize())
increaseBoardSize();
PointList black =
new PointList(m_board.getSetup(BLACK));
PointList white =
new PointList(m_board.getSetup(WHITE));
white.add(GoPoint.get(x, y));
m_board.setup(black, white, m_board.getToMove());
++x;
}
else if (isEmpty(c))
{
if (x >= m_board.getSize())
increaseBoardSize();
++x;
}
else
break;
}
}
private String readLine() throws ParseError
{
try
{
return m_reader.readLine();
}
catch (IOException e)
{
return null;
}
}
private void shiftBoardDown(int deltaY)
{
int size = m_board.getSize();
Board newBoard = new Board(size);
PointList black = new PointList();
PointList white = new PointList();
for (int y = 0; y < size - deltaY; ++y)
for (int x = 0; x < size; ++x)
{
GoColor c = m_board.getColor(GoPoint.get(x, y + deltaY));
GoPoint p = GoPoint.get(x, y);
if (c == BLACK)
black.add(p);
else if (c == WHITE)
white.add(p);
}
newBoard.setup(black, white, m_board.getToMove());
m_board = newBoard;
}
}