package fr.xtof54.sgfsearch; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.util.Enumeration; import java.util.Vector; import rene.util.list.ListElement; import rene.util.parser.StringParser; import rene.util.xml.XmlReader; import rene.util.xml.XmlReaderException; import rene.util.xml.XmlTag; import rene.util.xml.XmlTagPI; import rene.util.xml.XmlTagText; import rene.util.xml.XmlTree; import rene.util.xml.XmlWriter; /** This is a class wich contains a TreeNode. It used to store complete game trees. @see jagoclient.board.TreeNode */ public class SGFTree { protected TreeNode History; // the game history /** initlialize with a specific Node */ public SGFTree (Node n) { History=new TreeNode(n); History.node().main(true); } /** return the top node of this game tree */ public TreeNode top () { return History; } final int maxbuffer=4096; char[] Buffer=new char[maxbuffer]; // the buffer for reading of files int BufferN; BoardInterface GF; char readnext (BufferedReader in) throws IOException { int c=readchar(in); while (c=='\n' || c=='\t' || c==' ') { if (c==-1) throw new IOException(); c=readchar(in); } return (char)c; } static int lastnl=0; char readchar (BufferedReader in) throws IOException { int c; while (true) { c=in.read(); if (c==-1) throw new IOException(); if (c==13) { if (lastnl==10) lastnl=0; else { lastnl=13; return '\n'; } } else if (c==10) { if (lastnl==13) lastnl=0; else { lastnl=10; return '\n'; } } else { lastnl=0; return (char)c; } } } // read a node assuming that ; has been found // return the character, which did not fit into node properties, // usually ;, ( or ) char readnode (TreeNode p, BufferedReader in) throws IOException { boolean sgf=GF.getParameter("sgfcomments",false); char c=readnext(in); Action a; Node n=new Node(((Node)p.content()).number()); String s; loop: while (true) // read all actions { BufferN=0; while (true) { if (c>='A' && c<='Z') store(c); // note only capital letters else if (c=='(' || c==';' || c==')') break loop; // last property reached // BufferN should be 0 then else if (c=='[') break; // end of porperty type, arguments starting else if (c<'a' || c>'z') throw new IOException(); // this is an error c=readnext(in); } if (BufferN==0) throw new IOException(); s=new String(Buffer,0,BufferN); if (s.equals("L")) a=new LabelAction(GF); else if (s.equals("M")) a=new MarkAction(GF); else a=new Action(s); while (c=='[') { BufferN=0; while (true) { c=readchar(in); if (c=='\\') { c=readchar(in); if (sgf && c=='\n') { if (BufferN>1 && Buffer[BufferN-1]==' ') continue; else c=' '; } } else if (c==']') break; store(c); } c=readnext(in); // prepare next argument String s1; if (BufferN>0) s1=new String(Buffer,0,BufferN); else s1=""; if (!expand(a,s1)) a.addargument(s1); } // no more arguments n.addaction(a); if (a.type().equals("B") || a.type().equals("W")) { n.number(n.number()+1); } } // end of actions has been found // append node n.main(p); TreeNode newp; if (((Node)p.content()).actions()==null) p.content(n); else { p.addchild(newp=new TreeNode(n)); n.main(p); p=newp; if (p.parentPos()!=null && p!=p.parentPos().firstChild()) ((Node)p.content()).number(2); } return c; } /** Store c into the Buffer extending its length, if necessary. This is a fix by Bogdar Creanga from 2000-10-17 (Many Thanks) */ private void store (char c) { try { Buffer[BufferN]=c; BufferN++; } catch (ArrayIndexOutOfBoundsException e) { int newLength = Buffer.length + maxbuffer; char[] newBuffer = new char[newLength]; System.arraycopy(Buffer,0,newBuffer,0,Buffer.length); Buffer = newBuffer; Buffer[BufferN++]=c; } } // Check for the terrible compressed point list and expand into // single points boolean expand (Action a, String s) { String t=a.type(); if (!(t.equals("MA") || t.equals("SQ") || t.equals("TR") || t.equals("CR") || t.equals("AW") || t.equals("AB") || t.equals("AE") || t.equals("SL"))) return false; if (s.length()!=5 || s.charAt(2)!=':') return false; String s0=s.substring(0,2),s1=s.substring(3); int i0=Field.i(s0),j0=Field.j(s0); int i1=Field.i(s1),j1=Field.j(s1); if (i1<i0 || j1<j0) return false; int i,j; for (i=i0; i<=i1; i++) for (j=j0; j<=j1; j++) { a.addargument(Field.string(i,j)); } return true; } /** Read the nodes belonging to a tree. this assumes that ( has been found. */ void readnodes (TreeNode p, BufferedReader in) throws IOException { char c=readnext(in); while (true) { if (c==';') { c=readnode(p,in); if (p.haschildren()) p=p.lastChild(); continue; } else if (c=='(') { readnodes(p,in); } else if (c==')') break; c=readnext(in); } } /** Read the tree from an BufferedReader in SGF format. The BoardInterfaces is only used to determine the "sgfcomments" parameter. */ public static Vector load (BufferedReader in, BoardInterface gf) throws IOException { Vector v=new Vector(); boolean linestart=true; int c; reading : while (true) { SGFTree T=new SGFTree(new Node(1)); while (true) // search for ( at line start { try { c=T.readchar(in); } catch (IOException ex) { break reading; } if (linestart && c=='(') break; if (c=='\n') linestart=true; else linestart=false; } T.GF=gf; T.readnodes(T.History,in); // read the nodes v.addElement(T); } return v; } /* XML Reader Stuff */ // Assumption on Boardsize of xml file, if <BoardSize> is not found static int BoardSize=19; static String GameName=""; /** Read all games from a tree. @return Vector of trees. */ static Vector readnodes (XmlTree tree, BoardInterface gf) throws XmlReaderException { Vector v=new Vector(); Enumeration root=tree.getContent(); while (root.hasMoreElements()) { tree=(XmlTree)root.nextElement(); XmlTag tag=tree.getTag(); if (tag instanceof XmlTagPI) continue; testTag(tag,"Go"); Enumeration trees=tree.getContent(); while (trees.hasMoreElements()) { tree=(XmlTree)trees.nextElement(); tag=tree.getTag(); testTag(tag,"GoGame"); if (tag.hasParam("name")) { GameName=tag.getValue("name"); } Enumeration e=tree.getContent(); if (!e.hasMoreElements()) xmlMissing("Information"); XmlTree information=(XmlTree)e.nextElement(); testTag(information.getTag(),"Information"); getBoardSize(information); SGFTree t=new SGFTree(new Node(1)); t.GF=gf; TreeNode p=t.readnodes(e,null,tree,true,1); if (p!=null) setInformation(p,information); t.History=p; if (p!=null) v.addElement(t); } } return v; } public static void setInformation (TreeNode p, XmlTree information) throws XmlReaderException { Enumeration e=information.getContent(); while (e.hasMoreElements()) { XmlTree tree=(XmlTree)e.nextElement(); XmlTag tag=tree.getTag(); if (tag.name().equals("BoardSize")) { p.addaction(new Action("SZ",""+BoardSize)); } else if (tag.name().equals("BlackPlayer")) { p.addaction(new Action("PB",getText(tree))); } else if (tag.name().equals("BlackRank")) { p.addaction(new Action("BR",getText(tree))); } else if (tag.name().equals("WhitePlayer")) { p.addaction(new Action("PW",getText(tree))); } else if (tag.name().equals("WhiteRank")) { p.addaction(new Action("WR",getText(tree))); } else if (tag.name().equals("Date")) { p.addaction(new Action("DT",getText(tree))); } else if (tag.name().equals("Time")) { p.addaction(new Action("TM",getText(tree))); } else if (tag.name().equals("Komi")) { p.addaction(new Action("KM",getText(tree))); } else if (tag.name().equals("Result")) { p.addaction(new Action("RE",getText(tree))); } else if (tag.name().equals("Handicap")) { p.addaction(new Action("HA",getText(tree))); } else if (tag.name().equals("User")) { p.addaction(new Action("US",getText(tree))); } else if (tag.name().equals("Copyright")) { p.addaction(new Action("CP",parseComment(tree))); } } if (!GameName.equals("")) p.addaction(new Action("GN",GameName)); } public static String getText (XmlTree tree) throws XmlReaderException { Enumeration e=tree.getContent(); if (!e.hasMoreElements()) return ""; XmlTree t=(XmlTree)e.nextElement(); XmlTag tag=t.getTag(); if (!(tag instanceof XmlTagText) || e.hasMoreElements()) throw new XmlReaderException( "<"+tree.getTag().name()+"> has wrong content."); return ((XmlTagText)tag).getContent(); } public static void getBoardSize (XmlTree tree) throws XmlReaderException { Enumeration e=tree.getContent(); BoardSize=19; while (e.hasMoreElements()) { tree=(XmlTree)e.nextElement(); if (tree.getTag().name().equals("BoardSize")) { tree=tree.xmlFirstContent(); XmlTag tag=tree.getTag(); if (tag instanceof XmlTagText) { try { BoardSize=Integer.parseInt( ((XmlTagText)tag).getContent()); } catch (Exception ex) { throw new XmlReaderException( "Illegal <BoardSize>"); } } else throw new XmlReaderException( "Illegal <BoardSize>"); break; } } } TreeNode readnodes (Enumeration e, TreeNode p, XmlTree father, boolean main, int number) throws XmlReaderException { TreeNode ret=null; while (e.hasMoreElements()) { XmlTree tree=(XmlTree)e.nextElement(); XmlTag tag=tree.getTag(); if (tag.name().equals("Nodes")) { return readnodes(tree.getContent(),p,father,main,number); } else if (tag.name().equals("Node")) { if (p!=null) number=((Node)p.content()).number(); Node n=readnode(number,tree); n.main(main); TreeNode newp=new TreeNode(n); if (p==null) ret=newp; if (p!=null) p.addchild(newp); p=newp; } else if (tag.name().equals("White")) { if (p!=null) number=((Node)p.content()).number(); Node n=new Node(number); try { n.addaction(new Action("W",xmlToSgf(tree))); n.number(n.number()+1); n.main(main); } catch (XmlReaderException ey) { n.addaction(new Action("C","Pass")); } if (tag.hasParam("name")) { n.addaction(new Action("N",tag.getValue("name"))); } if (tag.hasParam("timeleft")) { n.addaction(new Action("WL",tag.getValue("timeleft"))); } TreeNode newp=new TreeNode(n); if (p==null) ret=newp; if (p!=null) p.addchild(newp); p=newp; } else if (tag.name().equals("Black")) { if (p!=null) number=((Node)p.content()).number(); Node n=new Node(number); try { n.addaction(new Action("B",xmlToSgf(tree))); n.number(n.number()+1); n.main(main); } catch (XmlReaderException ey) { n.addaction(new Action("C","Pass")); } if (tag.hasParam("name")) { n.addaction(new Action("N",tag.getValue("name"))); } if (tag.hasParam("timeleft")) { n.addaction(new Action("BL",tag.getValue("timeleft"))); } TreeNode newp=new TreeNode(n); if (p==null) ret=newp; if (p!=null) p.addchild(newp); p=newp; } else if (tag.name().equals("Comment")) { if (p==null) { Node n=new Node(number); n.main(main); p=new TreeNode(n); ret=p; } Node n=(Node)p.content(); n.addaction(new Action("C",parseComment(tree))); } else if (tag.name().equals("Variation")) { TreeNode parent=(TreeNode)p.parent(); if (parent==null) throw new XmlReaderException("Root node cannot have variation"); TreeNode newp=readnodes(tree.getContent(),null,tree,false,1); parent.addchild(newp); } else { throw new XmlReaderException( "Illegal Node or Variation <"+tag.name()+">"); } } return ret; } public Node readnode (int number, XmlTree tree) throws XmlReaderException { Node n=new Node(number); XmlTag tag=tree.getTag(); if (tag.hasParam("name")) { n.addaction(new Action("N",tag.getValue("name"))); } if (tag.hasParam("blacktime")) { n.addaction(new Action("BL",tag.getValue("blacktime"))); } if (tag.hasParam("whitetime")) { n.addaction(new Action("WL",tag.getValue("whitetime"))); } Enumeration e=tree.getContent(); while (e.hasMoreElements()) { XmlTree t=(XmlTree)e.nextElement(); tag=t.getTag(); if (tag.name().equals("Black")) { try { n.addaction(new Action("B",xmlToSgf(t))); n.number(n.number()+1); } catch (XmlReaderException ey) {} } else if (tag.name().equals("White")) { try { n.addaction(new Action("W",xmlToSgf(t))); n.number(n.number()+1); } catch (XmlReaderException ey) {} } else if (tag.name().equals("AddBlack")) { n.addaction(new Action("AB",xmlToSgf(t))); } else if (tag.name().equals("AddWhite")) { n.addaction(new Action("AW",xmlToSgf(t))); } else if (tag.name().equals("Delete")) { n.expandaction(new Action("AE",xmlToSgf(t))); } else if (tag.name().equals("Mark")) { if (tag.hasParam("type")) { String s=tag.getValue("type"); if (s.equals("triangle")) { n.expandaction(new Action("TR",xmlToSgf(t))); } else if (s.equals("square")) { n.expandaction(new Action("SQ",xmlToSgf(t))); } else if (s.equals("circle")) { n.expandaction(new Action("CR",xmlToSgf(t))); } } else if (tag.hasParam("label")) { String s=tag.getValue("label"); n.expandaction(new Action("LB", xmlToSgf(t)+":"+s)); } else if (tag.hasParam("territory")) { String s=tag.getValue("territory"); if (s.equals("white")) { n.expandaction(new Action("TW",xmlToSgf(t))); } else if (s.equals("black")) { n.expandaction(new Action("TB",xmlToSgf(t))); } } else n.expandaction(new MarkAction(xmlToSgf(t),GF)); } else if (tag.name().equals("BlackTimeLeft")) { n.addaction(new Action("BL",getText(t))); } else if (tag.name().equals("WhiteTimeLeft")) { n.addaction(new Action("WL",getText(t))); } else if (tag.name().equals("Comment")) { n.addaction(new Action("C",parseComment(t))); } else if (tag.name().equals("SGF")) { if (!tag.hasParam("type")) throw new XmlReaderException("Illegal <SGF> tag."); Action a; if (tag.getValue("type").equals("M")) a=new MarkAction(GF); else a=new Action(tag.getValue("type")); Enumeration eh=t.getContent(); while (eh.hasMoreElements()) { XmlTree th=(XmlTree)eh.nextElement(); XmlTag tagh=th.getTag(); if (!tagh.name().equals("Arg")) throw new XmlReaderException("Illegal <SGF> tag."); if (!th.isText()) throw new XmlReaderException("Illegal <SGF> tag."); else a.addargument(th.getText()); } n.addaction(a); } } return n; } public static String parseComment (XmlTree t) throws XmlReaderException { StringBuffer s=new StringBuffer(); Enumeration e=t.getContent(); while (e.hasMoreElements()) { XmlTree tree=(XmlTree)e.nextElement(); XmlTag tag=tree.getTag(); if (tag.name().equals("P")) { if (!tree.haschildren()) s.append("\n"); else { XmlTree h=tree.xmlFirstContent(); String k=((XmlTagText)h.getTag()).getContent(); k=k.replace('\n',' '); StringParser p=new StringParser(k); Vector v=p.wraplines(1000); for (int i=0; i<v.size(); i++) { s.append((String)v.elementAt(i)); s.append("\n"); } } } else if (tag instanceof XmlTagText) { String k=((XmlTagText)tag).getContent(); k=k.replace('\n',' '); StringParser p=new StringParser(k); Vector v=p.wraplines(1000); for (int i=0; i<v.size(); i++) { s.append((String)v.elementAt(i)); s.append("\n"); } } else throw new XmlReaderException("<"+tag.name()+"> not proper here."); } return s.toString(); } public String xmlToSgf (XmlTree tree) throws XmlReaderException { XmlTag tag=tree.getTag(); if (tag.hasParam("at")) { return xmlToSgf(tag.getValue("at")); } Enumeration e=tree.getContent(); if (!e.hasMoreElements()) throw new XmlReaderException("Missing board position."); tag=((XmlTree)e.nextElement()).getTag(); if (tag instanceof XmlTagText) { String pos=((XmlTagText)tag).getContent(); return xmlToSgf(pos); } else if (tag.name().equals("at")) { String pos=((XmlTagText)tag).getContent(); return xmlToSgf(pos); } else throw new XmlReaderException(tag.name() +" contains wrong board position."); } public String xmlToSgf (String pos) throws XmlReaderException { if (pos.length()<2) wrongBoardPosition(pos); int n=pos.indexOf(","); if (n>0 && n<pos.length()) { String s1=pos.substring(0,n),s2=pos.substring(n+1); try { int i=Integer.parseInt(s1)-1; int j=Integer.parseInt(s2); j=BoardSize-j; if (i<0 || i>=BoardSize || j<0 || j>=BoardSize) wrongBoardPosition(pos); return Field.string(i,j); } catch (Exception ex) { wrongBoardPosition(pos); } } char c=Character.toUpperCase(pos.charAt(0)); if (c>='J') c--; int i=c-'A'; int j=0; try { j=Integer.parseInt(pos.substring(1)); } catch (Exception ex) { wrongBoardPosition(pos); } j=BoardSize-j; if (i<0 || i>=BoardSize || j<0 || j>=BoardSize) wrongBoardPosition(pos); return Field.string(i,j); } public void wrongBoardPosition (String s) throws XmlReaderException { throw new XmlReaderException("Wrong Board Position "+s); } public static void xmlMissing (String s) throws XmlReaderException { throw new XmlReaderException("Missing <"+s+">"); } public static void testTag (XmlTag tag, String name) throws XmlReaderException { if (!tag.name().equals(name)) { throw new XmlReaderException( "<"+name+"> expected instead of <"+tag.name()+">"); } } /** Read a number of trees from an XML file. */ public static Vector load (XmlReader xml, BoardInterface gf) throws XmlReaderException { XmlTree t=xml.scan(); if (t==null) throw new XmlReaderException("Illegal file format"); Vector v=readnodes(t,gf); return v; } /** Print the tree to the specified PrintWriter. @param p the subtree to be printed */ void printtree (TreeNode p, PrintWriter o) { o.println("("); while (true) { p.node().print(o); if (!p.haschildren()) break; if (p.lastChild()!=p.firstChild()) { ListElement e=p.children().first(); while (e!=null) { printtree((TreeNode)e.content(),o); e=e.next(); } break; } p=p.firstChild(); } o.println(")"); } /** Print the tree to the specified PrintWriter. @param p the subtree to be printed */ void printtree (TreeNode p, XmlWriter xml, int size, boolean top) { if (top) { String s=p.getaction("GN"); if (s!=null && !s.equals("")) xml.startTagNewLine("GoGame","name",s); else xml.startTagNewLine("GoGame"); xml.startTagNewLine("Information"); printInformation(xml,p,"AP","Application"); printInformation(xml,p,"SZ","BoardSize"); printInformation(xml,p,"PB","BlackPlayer"); printInformation(xml,p,"BR","BlackRank"); printInformation(xml,p,"PW","WhitePlayer"); printInformation(xml,p,"WR","WhiteRank"); printInformation(xml,p,"DT","Date"); printInformation(xml,p,"TM","Time"); printInformation(xml,p,"KM","Komi"); printInformation(xml,p,"RE","Result"); printInformation(xml,p,"HA","Handicap"); printInformation(xml,p,"US","User"); printInformationText(xml,p,"CP","Copyright"); xml.endTagNewLine("Information"); } else xml.startTagNewLine("Variation"); if (top) xml.startTagNewLine("Nodes"); while (true) { p.node().print(xml,size); if (!p.haschildren()) break; if (p.lastChild()!=p.firstChild()) { ListElement e=p.children().first(); p=p.firstChild(); p.node().print(xml,size); e=e.next(); while (e!=null) { printtree((TreeNode)e.content(),xml,size,false); e=e.next(); } if (!p.haschildren()) break; } p=p.firstChild(); } if (top) xml.endTagNewLine("Nodes"); if (top) xml.endTagNewLine("GoGame"); else xml.endTagNewLine("Variation"); } public void printInformation (XmlWriter xml, TreeNode p, String tag, String xmltag) { String s=p.getaction(tag); if (s!=null && !s.equals("")) xml.printTagNewLine(xmltag,s); } public void printInformationText (XmlWriter xml, TreeNode p, String tag, String xmltag) { String s=p.getaction(tag); if (s!=null && !s.equals("")) { xml.startTagNewLine(xmltag); xml.printParagraphs(s,60); xml.endTagNewLine(xmltag); } } /** Print this tree to the PrintWriter starting at the root node. */ public void print (PrintWriter o) { printtree(History,o); } public void printXML (XmlWriter xml) { printtree(History,xml,getSize(),true); } public int getSize () { try { return Integer.parseInt(History.getaction("SZ")); } catch (Exception e) { return 19; } } }