home *** CD-ROM | disk | FTP | other *** search
- // copying
- // Copyright (C) 1991 David Stoutamire (daves@alpha.ces.cwru.edu)
- //
- // This program is free software; you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, version 2.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program (file "COPYING"); if not, write to the Free
- // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- #include "iku.h"
-
- // class Pos
- // class Pos simply indicates a position on the board.
- // This may also be 'invalid'.
- // constructors
- Pos::Pos(String s) {
- // construct from string in Korschelt notation...
- char c=s[0];
- int r=atoi((char*)String(s) + 1);
-
- if (isupper(c))
- c=tolower(c);
- if (c<'a'||c=='i'||c>'t'||r<1||r>19) { // invalid position
- row=(-1);
- col=(-1);
- }
- else {
- if (c>'i') c--;
- row=r-1;
- col=c-'a';
- }
- }
- Pos::Pos(int r, int c) {
- // Note that although Korschelt notation is <col,row>,
- // all references
- // with explicit numbers are given <row,col>.
- row=r;
- col=c;
- }
- Pos::Pos() {
- // Any row, col of (-1) signals a bad position
- row=(-1);
- col=(-1);
- }
- // members
- bool Pos::valid() {
- // is it a valid position?
- return(row>=0&&row<=18&&col>=0&&col<=18);
- }
- bool Pos::operator== (Pos vs) {
- return(row==vs.row&&col==vs.col);
- }
- bool Pos::operator!= (Pos vs) {
- return(row!=vs.row||col!=vs.col);
- }
-
- // up, down, left and right return corresponding positions.
- // these MAY be invalid...
- Pos Pos::up() { return Pos(row+1,col); }
- Pos Pos::down() { return Pos(row-1,col); }
- Pos Pos::left() { return Pos(row,col-1); }
- Pos Pos::right() { return Pos(row,col+1); }
- // friends
- ostream& operator<< (ostream& s, Pos p) {
- s << p.str();
- return(s);
- }
- istream& operator>> (istream& s, Pos& p) {
- // Positions may be >> with the caveat that after a bad read
- // (an invalid position) future reads may all be bad.
- // Watch out for infinite reading-in loops!
- char c;
- int r;
-
- s >> c >> r;
- p=Pos(String(c)+String(r));
- return(s);
- }
- String Pos::str() {
- // Moves are printed in Korschelt or as the string "BadPos".
- if (!valid())
- return("BadPos");
- else
- return String(chr('a'+col+((col>7)?1:0)))+String(dec(row+1));
- }
- // class Move
- // class Move is a move, like a play, resignation, pass, etc.
- // May be 'invalid'.
- // constructors
- Move::Move() {
- // constructor for invalid (default) move
- kind=invalid;
- where=Pos();
- }
- Move::Move(moveenum k,Pos at=Pos()) {
- // basic constructor
- kind=k;
- where=at;
- }
- Move::Move(String s) {
- // Attempts to translate s into a move.
- // If something is wrong, returns
- // an invalid move. Check with valid().
- // Can parse COSMOS-style and Ishi Standard Format moves.
- s=s.after(
- Regex("^[ \n\t]*[0-9]*[ \n\t]*[BWbw][ \n\t]*[0-9]*[ \n\t]*"));
- if (s.contains(RXwhite))
- s=s.before(RXwhite);
- if (fcompare(s,"pass")==0) {
- kind=pass;
- }
- else if (fcompare(s,"resign")==0) {
- kind=resign;
- }
- else {
- if (s.matches(Regex("[a-hj-tA-HJ-T][0-9]+"))&&Pos(s).valid()) {
- kind=play;
- where=Pos(s);
- }
- else {
- kind=invalid;
- }
- }
- // ATTN movecomment="";
- }
- // members
- bool Move::valid() {
- return(kind!=invalid);
- }
- bool Move::operator== (Move vs) {
- if (kind==invalid&&vs.kind==invalid) // if both are invalid...
- return(true);
- if (kind==pass&&vs.kind==pass) // or both are passes...
- return(true);
- if (kind==resign&&vs.kind==resign) // or both are resignations...
- return(true);
- if (where==vs.where) // or if at same spot.
- return(true);
- return(false);
- }
- String Move::str() {
- // This is meant for interactive use,
- // not as an inverse for Move(String).
- switch(kind) {
- case invalid: return("Invalid move");
- case play: return(where.str());
- case pass: return("Pass");
- case resign: return("Resign");
- default:
- fatal("Problem in Move::str switch.");
- }
- }
-
- // class Block
- Block::Block(Board& b, Pos p) {
- int r=p.row;
- int c=p.col;
-
- color=b.board[p.row][p.col];
- stones.add(p);
- if (r>0&&b.board[r-1][c]==empty)
- liberties.add(Pos(r-1,c));
- if (r<18&&b.board[r+1][c]==empty)
- liberties.add(Pos(r+1,c));
- if (c>0&&b.board[r][c-1]==empty)
- liberties.add(Pos(r,c-1));
- if (c<18&&b.board[r][c+1]==empty)
- liberties.add(Pos(r,c+1));
- }
- Block::Block(Block& b) {
- stones=b.stones;
- liberties=b.liberties;
- color=b.color;
- }
- Block::Block() {
- }
- String Block::str() {
- String s(color==blackstone?"Black":"White");
- s+=" block, libs {";
- Pix p;
- foreach(p,liberties)
- s+=liberties(p).str()+" ";
- s+="}, stones {";
- foreach(p,stones)
- s+=stones(p).str()+" ";
- s+="}";
- return(s);
- }
-
- // class Board
- // class Board is the workhorse. It represents the state of the game at a
- // certain time, not just the way the board looks.
- // globals
- static Pos Board_tspot; // used for ko identification
- // constructors and destructors
- Board::Board(String hand="") {
- // Constructor for board representing beginning of game.
- // Handicap stones for black are in Korschelt in the string.
- // If there are handicap stones, white gets next move,
- // else if string is null then black plays.
- int i,j;
- doboard(i,j) {
- board[i][j]=empty;
- blocks[i][j]=0;
- }
- whitetaken=0;
- blacktaken=0;
- korestriction=Pos(); // illegal position means 'no restriction'
- movenum=1;
- gameover=false;
- if (hand=="") {
- comment="At beginning of game, no handicap.";
- turntoplay=black;
- }
- else {
- // a poorly formatted handicap string will GIGO...
- String parsed[hand.length()];
- int num=split(hand,parsed,hand.length(),RXwhite);
- comment="At beginning of game, "
- + String(dec(num)) + " stones placed.";
- for (int i=0;i<num;i++) {
- Pos p(parsed[i]);
- turntoplay=black;
- applymove(Move(play,p));
- movenum=1;
- }
- turntoplay=white;
- }
- }
- // members
- void Board::operator=(Board& b) {
- board=b.board;
- allBlocks=b.allBlocks;
- int i,j;
- doboard(i,j)
- blocks[i][j]=0;
- Pix p,q;
- foreach(p,allBlocks) {
- PosSLSet& pr=allBlocks(p).stones;
- foreach(q,pr) {
- Pos& qr=pr(q);
- blocks[qr.row][qr.col]=p;
- }
- }
- movenum=b.movenum;
- korestriction=b.korestriction;
- gameover=b.gameover;
- comment=b.comment;
- turntoplay=b.turntoplay;
- whitetaken=b.whitetaken;
- blacktaken=b.blacktaken;
- }
- Board::Board(Board& b) {
- (*this)=b;
- }
- char Board_chartoshow(int row, int col, boardenum what) {
- // utility function for deciding character to display for different
- // places on the board. Not really a member function.
- if (row==0&&col==0) {
- switch(what) {
- case empty: return('`'); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- else if (row==0&&col==18) {
- switch(what) {
- case empty: return('\''); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- else if (row==18&&col==0) {
- switch(what) {
- case empty: return('.'); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- else if (row==18&&col==18) {
- switch(what) {
- case empty: return('.'); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- else if (row==0||row==18) {
- switch(what) {
- case empty: return('-'); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- else if (col==0||col==18) {
- switch(what) {
- case empty: return('|'); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- else {
- switch(what) {
- case empty: return('+'); break;
- case black: return('@'); break;
- case white: return('O'); break;
- default: fatal("Bad value in chartoshow switch.");
- }
- }
- }
- String Board::str() {
- // Make a string representing board. This spans multiple lines,
- // of course, and contains capturing info and so on.
- String s=" a b c d e f g h j k l m n o p q r s t\n";
- for (int i=0;i<19;i++) {
- s+=form(" %2d ",19-i);
- for (int j=0;j<19;j++) {
- s+=(Board_chartoshow(18-i,j,board[18-i][j]));
- if (j<18)
- s+=('-');
- }
- s+=form(" %-2d ",19-i);
- switch(i) {
- case 0:
- if (gameover)
- s+="Game over.\n";
- else
- s+=form("Move #%d: %s to play.\n",movenum,
- (turntoplay==black)?"black":"white");
- break;
- case 2:
- s+=form("White taken: %d\n", whitetaken);
- break;
- case 3:
- s+=form("Black taken: %d\n", blacktaken);
- break;
- case 5:
- if (korestriction.valid())
- s+="Ko restriction at "+korestriction.str()+".";
- s+="\n";
- break;
- default:
- s+="\n";
- }
- }
- s+=" a b c d e f g h j k l m n o p q r s t\n\n";
- s+=comment+"\n";
- /*
- s+="\n";
- Pix blist[300];
- int numblocks=0;
- Pix p;
- foreach(p,allBlocks) {
- s+=String('a'+numblocks)+": "+allBlocks(p).str()+"\n";
- blist[numblocks++]=p;
- }
- for (i=0;i<19;i++) {
- for (int j=0;j<19;j++)
- s+=(char)(board[18-i][j]==empty?'.':('0'+libsat(Pos(18-i,j))));
- s+=" ";
- for (j=0;j<19;j++)
- s+=(char)(board[18-i][j]==empty?'.':('0'+sizeat(Pos(18-i,j))));
- s+=" ";
- for (j=0;j<19;j++) {
- bool found=false;
- if (blocks[18-i][j]!=0)
- for (int b=0;b<numblocks;b++) {
- if (blist[b]==blocks[18-i][j]) {
- s+='a'+b;
- found=true;
- }
- }
- if (!found) s+='.';
- }
- s+="\n";
- }
- s+="\n";
- */
- return(s);
- }
- int Board::sizeat(Pos p) {
- // sizeat returns size of group at p.
- // if invalid, this is just zero.
- if (!p.valid())
- return(0);
- if (blocks[p.row][p.col]==0)
- return(0);
- else
- return(allBlocks(blocks[p.row][p.col]).stones.length());
- }
- int Board::libsat(Pos p) {
- // libsat returns number of liberties of group at p.
- // If p is empty, or invalid, this is 0.
- if (!p.valid())
- return(0);
- if (blocks[p.row][p.col]==0)
- return(0);
- else
- return(allBlocks(blocks[p.row][p.col]).liberties.length());
- }
- bool Board::islegalmove(Move m) {
- // islegalmove returns true, for legal moves, false for illegal.
- if (m.kind==invalid) // if move is legit...
- return(false);
- if (gameover) // and game is still going...
- return(false);
- if (m.kind==pass) // and a pass...
- return(true);
- if (board[m.where.row][m.where.col]!=empty)
- return(false); // BUT if not empty, no good
- if (korestriction==m.where) // or if a ko restriction.
- return(false);
-
- if (allowsuicide) // If suicide ok, then fine
- return(true); // from here on in.
-
- // Now check if it's a suicide move.
- fatal("Suicide move disable not installed.");
- return(true);
- }
- void Board::applymove(Move m) {
- // apply move to board. This is a workhorse.
-
- int before=whitetaken+blacktaken;
- int r=m.where.row, c=m.where.col;
- stoneenum othercolor;
- if (turntoplay==blackstone)
- othercolor=whitestone;
- else
- othercolor=blackstone;
-
- if (m.kind==resign) { // resigns are pretty easy to deal with
- gameover=true;
- return;
- }
- else if (m.kind==play) {
- if (errortest) // check for error if turned on
- if (!islegalmove(m))
- fatal("Illegal move in Board::applymove.");
-
- // make a new block, and merge any that were adjacent
- board[r][c]=turntoplay;
- Pix myblock=allBlocks.prepend(Block(*this,m.where));
- blocks[r][c]=myblock;
- if (r>0&&board[r-1][c]==turntoplay)
- transfer(blocks[r-1][c],myblock);
- if (r<18&&board[r+1][c]==turntoplay)
- transfer(blocks[r+1][c],myblock);
- if (c>0&&board[r][c-1]==turntoplay)
- transfer(blocks[r][c-1],myblock);
- if (c<18&&board[r][c+1]==turntoplay)
- transfer(blocks[r][c+1],myblock);
-
- // remove this position from liberty lists of adjacents
- if (r>0&&blocks[r-1][c]!=0) {
- PosSLSet& s=allBlocks(blocks[r-1][c]).liberties;
- s.del(m.where);
- if (board[r-1][c]==othercolor&&s.length()==0)
- removeblock(blocks[r-1][c]);
- }
- if (r<18&&blocks[r+1][c]!=0) {
- PosSLSet& s=allBlocks(blocks[r+1][c]).liberties;
- s.del(m.where);
- if (board[r+1][c]==othercolor&&s.length()==0)
- removeblock(blocks[r+1][c]);
- }
- if (c>0&&blocks[r][c-1]!=0) {
- PosSLSet& s=allBlocks(blocks[r][c-1]).liberties;
- s.del(m.where);
- if (board[r][c-1]==othercolor&&s.length()==0)
- removeblock(blocks[r][c-1]);
- }
- if (c<18&&blocks[r][c+1]!=0) {
- PosSLSet& s=allBlocks(blocks[r][c+1]).liberties;
- s.del(m.where);
- if (board[r][c+1]==othercolor&&s.length()==0)
- removeblock(blocks[r][c+1]);
- }
-
- // make sure not a suicide
- if (allBlocks(myblock).liberties.length()==0)
- removeblock(myblock);
-
- // just one stone taken; ko?
- if (whitetaken+blacktaken==before+1&& sizeat(m.where)==1
- && libsat(m.where)==1)
- korestriction=Board_tspot;
- else
- korestriction=Pos();
- }
- turntoplay=othercolor;
- movenum++;
- }
- void Board::removeblock(Pix bl) {
- Block& b=allBlocks(bl);
- if (b.color==blackstone)
- blacktaken+=b.stones.length();
- else
- whitetaken+=b.stones.length();
- Pix p;
- foreach(p,b.stones) {
- Pos w(b.stones(p));
- Board_tspot=w;
-
- board[w.row][w.col]=empty;
- blocks[w.row][w.col]=0;
-
- // for each potential adjacent block, try to add
- // this as a liberty
- if (w.row>0&&blocks[w.row-1][w.col]!=0)
- allBlocks(blocks[w.row-1][w.col]).liberties.add(w);
- if (w.row<18&&blocks[w.row+1][w.col]!=0)
- allBlocks(blocks[w.row+1][w.col]).liberties.add(w);
- if (w.col>0&&blocks[w.row][w.col-1]!=0)
- allBlocks(blocks[w.row][w.col-1]).liberties.add(w);
- if (w.col<18&&blocks[w.row][w.col+1]!=0)
- allBlocks(blocks[w.row][w.col+1]).liberties.add(w);
- }
- allBlocks.del(bl);
- }
- void Board::transfer(Pix from, Pix to) {
- if (from==to)
- return;
- Block& bfrom=allBlocks(from);
- Block& bto=allBlocks(to);
- Pix p;
- foreach(p,bfrom.stones) {
- Pos t=bfrom.stones(p);
- blocks[t.row][t.col]=to;
- }
- bto.stones|=bfrom.stones;
- bto.liberties|=bfrom.liberties;
- allBlocks.del(from);
- }
- // class Game
- // class Game represents a (possibly unfinished) game.
- // Score, etc. when known.
- // globals used by class Game
- Regex Game_avoidables =
- "^[ \n\t]*\\(\\(!\\)\\|\\(UNMARK\\)\\|\\($\\)\\|\\(\\*\\)\\|\\(EVENT\\)\\|\\(BOARDSIZE\\)\\|\\(MARK\\)\\|\\(PRISONER\\)\\)";
- Regex Game_begins = "^[ \n\t]*\\(\\(COM\\)\\|\\(VAR\\)\\)";
- Regex Game_ends = "^[ \n\t]*\\(\\(ENDCOM\\)\\|\\(ENDVAR\\)\\)";
- // constructor
- Game::Game(String filename) {
- // Try to load game 'filename'.
- // If a problem, such as file not readable,
- // fatal error.
- komi=0.0; // ATTN: komi, score are unparsed (not in Ishi format).
- comment="Uninitialized comment for file "+filename;
- result=unknown;
- finalscore=0.0;
- currentidx=0;
- nummoves=0;
-
- #ifndef IRIX
- File f(filename,io_readonly,a_useonly);
- if (!f.readable()) {
- fatal("Could not open file "+filename+".");
- }
- #else /* IRIX */
- FILE *fp;
- fp=fopen((char*)("games/"+filename),"r");
- #endif /* IRIX */
-
- #ifndef IRIX
- char *cp;
- #else /* IRIX */
- char cp[1000];
- #endif /* IRIX */
- String s;
- int level=0,line=0;
- currentidx=0;
- #ifndef IRIX
- f.gets(&cp);
- s=cp;
- free(cp);
- while (!f.eof()) {
- #else /* IRIX */
- while (fgets(cp,1000,fp)!=NULL) {
- cp[strlen(cp)-1]='\0';
- s=cp;
- #endif /* IRIX */
- line++;
- #ifdef IRIX
- if (s.length()==0)
- continue;
- #endif /* IRIX */
- if (!s.contains(Game_avoidables)) {
- if (s.contains(Game_begins))
- level++;
- else if (s.contains(Game_ends))
- level--;
- else if (s.contains("SETUP B")&&level==0&&nummoves==0) {
- s=s.after("SETUP B");
- while (s[0]==' ')
- s.at(0,1)="";
- current=Board(s);
- handicap=s;
- }
- else if (level==0) {
- Move m(s);
- if (!m.valid()) {
- fatal("Illegal syntax in " + filename + " line #"
- + String(dec(line)) + ": " + s);
- }
- if (nummoves>Game_maxmoves) {
- fatal("Too many moves to load in.");
- }
- nummoves++;
- if (!current.islegalmove(m)) {
- fatal("Illegal move in " + filename + " line #"
- + String(dec(line)) + ": " + s);
- }
- current.applymove(m);
- currentidx++;
- moves[nummoves-1]=m;
- }
- }
- #ifndef IRIX
- f.gets(&cp);
- s=cp;
- free(cp);
- #endif /* IRIX */
- }
- #ifdef IRIX
- //cout << filename << " had " << dec(nummoves) << "\n";
- fclose(fp);
- #endif /* IRIX */
- comment="Game loaded from file " + filename;
- }
- // members
- float Game::score() {
- return(finalscore - komi);
- }
- Board& Game::snapshot(int idx) {
- while (currentidx!=idx) {
- if (idx<currentidx) {
- current=Board(handicap);
- currentidx=0;
- break;
- }
- current.applymove(moves[currentidx]);
- currentidx++;
- if (currentidx>nummoves)
- fatal("Off the move list in snapshot.");
- }
- return current;
- };
- String Game::str() {
- String s;
- s+="EVENT\nBOARDSIZE 19\nCOM\n";
- s+=form("Komi: %g\n",komi);
- s+=form("Score: %g\n",score());
- s+="Handicap: "+handicap+"\n";
- s+="Result: ";
- switch(result) {
- case blacklost: s+="Black lost"; break;
- case whitelost: s+="White lost"; break;
- case blackresigned: s+="Black resigned"; break;
- case whiteresigned: s+="White resigned"; break;
- case unfinished: s+="Unfinished"; break;
- case tied: s+="Tied"; break;
- case unknown: s+="Unknown"; break;
- default:
- fatal("Bad in result switch: " + String(dec(result))+".");
- }
- s+="\nComment: "+comment;
- s+=form("\nNumber of moves: %d\n",nummoves);
- s+="Board at end of game:\n";
- s+=snapshot(nummoves).str();
- if (handicap!="")
- s+="SETUP B "+handicap+"\n";
- s+="ENDCOM\n";
- for (int i=0;i<nummoves;i++)
- s+=form("%c %d ",(snapshot(i).turntoplay==blackstone)?'B':'W',i+1);
- s+=moves[i].str()+"\n";
- return(s);
- }
-