home *** CD-ROM | disk | FTP | other *** search
- // documentation
- // 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.
- //
- // This files holds all the code associated with the Opponent
- // class. An Opponent is an object which can accept a description
- // of the state of the game and recommend a move.
-
- // includes
- #include "iku.h"
- #include <MLCG.h>
- #include <Uniform.h>
- #include <RandomInteger.h> // I believe this changes in g++ 1.39
-
- // variables
- static MLCG rgen; // Used for random number generation
- static Uniform rnd(0.0,1.0,&rgen);
-
- // class Opponent
- void Opponent::evaluate(Board& b) {
- // evaluate gets an evaluation for each legal move on the board,
- // and marks which are playable. Two techniques are defined
- // to facilitate building new Opponents. The evaluate call
- // gets a Board and is expected to rank all moves at once.
- // It defaults to caling evalpos, which is a move-at-a-time
- // evaluator. Using evalpos thus eliminates the outer loop
- // and reference to play{evals,able}[][].
- int i,j;
- doboard(i,j) {
- Pos p(i,j);
- if (b.islegalmove(Move(play,p))) {
- playevals[i][j]=evalpos(b,p);
- playable[i][j]=true;
- }
- else
- playable[i][j]=false;
- }
- }
- float Opponent::evalpos(Board& b, Pos p) {
- fatal("default evaluate(Board,Pos) called.");
- }
- Move Opponent::getmove(Board& b) {
- // simply returns the best ranking move.
- evaluate(b);
- float best=reallylowfloat;
- moveenum kind=play;
- int bestrow=0, bestcol=0;
- int i,j;
-
- doboard(i,j)
- if (playable[i][j]&&best<playevals[i][j]) {
- bestrow=i;
- bestcol=j;
- best=playevals[i][j];
- }
-
- if (best<0.0)
- kind=pass;
-
- return Move(kind,Pos(bestrow,bestcol));
- }
- void Opponent::docheckpt() {
- // This saves state for those opponents which need it.
- }
- static Opponent* opPosptcomp=NULL;
- static int Posptcomp(Pos* a, Pos* b) {
- float t=opPosptcomp->playevals[a->row][a->col]
- -opPosptcomp->playevals[b->row][b->col];
- if (t>0.0)
- return(-1);
- else if (t<0.0)
- return(1);
- else
- return(0);
- }
- void Opponent::study(Board& b, Move m) {
- // this routine is where the action is during learning.
- if (m.kind==resign)
- fatal("Asked to study a resignation in Opponent::study.");
-
- // evaluate moves and report the result.
- evaluate(b);
- takescore(b.movenum,score(m));
-
- float target=0.0;
- if (m.kind==play)
- target=playevals[m.where.row][m.where.col];
-
- int count=0,i,j=0;
- if (dolearn) {
- doboard(i,j)
- if (playable[i][j])
- count++;
- float rec=1/((float)count+1); // legal moves + pass
-
- int bad=0;
- doboard(i,j)
- if (playable[i][j]&&playevals[i][j]>=target) {
- bad++;
- if (playevals[i][j]==target)
- slide(b,Pos(i,j),-rec*0.5);
- else {
- bad++;
- slide(b,Pos(i,j),-rec);
- }
- }
-
- if (target<=0.0)
- bad++;
- if (target<0.0)
- bad++;
-
- if (m.kind==play)
- slide(b,m.where,rec*bad*0.5);
- }
-
- if (evalfile!="nofile") {
- Pos worst[361];
- int next=0;
- doboard(i,j)
- if (playable[i][j]) {
- worst[next++]=Pos(i,j);
- }
- opPosptcomp=this;
- qsort((char*)worst,next,sizeof(Pos),Posptcomp);
- FILE *psfp=NULL;
-
- // make postscript file if wanted
- if (postscriptdir!="nodir") {
- psfp=fopen((char*)(postscriptdir+"/"+dec(b.movenum)),"w");
- fprintf(psfp,"%%%%BoundingBox: 160 290 440 570\n");
- int j,k;
- doboard(j,k)
- if (b.board[j][k]==blackstone)
- fprintf(psfp,"%d %d () B\n",k+1,j+1);
- else if (b.board[j][k]==whitestone)
- fprintf(psfp,"%d %d () W\n",k+1,j+1);
- }
-
- // make COSMOS format file
- FILE *fp=fopen((char*)evalfile,"a");
- fprintf(fp,"%c %d ",(b.turntoplay==blackstone)?'B':'W',b.movenum);
- fprintf(fp,"%s\n",(char *)m.str());
- float last=reallylowfloat;
- int rank=0,after=0;
- for (i=0;i<next;i++) {
- float now=playevals[worst[i].row][worst[i].col];
- if (now!=last) {
- if (now<target)
- after++;
- if (after>=10)
- break;
- rank=i+1;
- }
- fprintf(fp,"MARK %d@%s\n",rank,(char *)worst[i].str());
- last=now;
- if (psfp!=NULL)
- if (m.where.row==worst[i].row&&m.where.col==worst[i].col)
- if (b.turntoplay==blackstone)
- fprintf(psfp,"%d %d (%d) MB\n",worst[i].col+1,worst[i].row+1,rank);
- else
- fprintf(psfp,"%d %d (%d) MW\n",worst[i].col+1,worst[i].row+1,rank);
- else
- fprintf(psfp,"%d %d (%d) C\n",worst[i].col+1,worst[i].row+1,rank);
- }
- if (psfp!=NULL) {
- fprintf(psfp,"showpage\n");
- fclose(psfp);
- }
- /*
- fprintf(fp,"COM");
- last=reallylowfloat;
- for (i=0;i<next;i++) {
- float now=playevals[worst[i].row][worst[i].col];
- if (now!=last)
- fprintf(fp,"\n\n%d - %g\n",i+1,now);
- last=now;
- }
- fprintf(fp,"\nENDCOM\n");
- */
- fclose(fp);
- }
- }
- void Opponent::slide(Board& b, Pos p, float deriv) {
- // slide is how the gradient gets communicated back to
- // the opponent.
-
- // fatal("Default slide() called.");
- }
- float Opponent::score(Move m) {
- // this computes the error of the evaluation.
- float target;
- int numwrong=0;
- int numseen=0;
- if (m.kind==play)
- target=playevals[m.where.row][m.where.col];
- else if (m.kind==pass)
- target=0.0;
- else if (m.kind==resign) // how should resignations be handled?
- fatal("Asked to score a resignation.");
- else
- fatal("Bad kind in printscore.");
- int i,j;
- doboard(i,j) {
- if (playable[i][j]) {
- if (playevals[i][j]>target)
- numwrong++;
- if (playevals[i][j]>=target)
- numwrong++;
- numseen++;
- }
- }
- if (m.kind==play) {
- if (target<0.0)
- numwrong++;
- if (target<=0.0)
- numwrong++;
- }
- numseen++;
- /*
- if (printevals) {
- for (i=0;i<19;i++) {
- for (j=0;j<19;j++)
- if (playable[i][j])
- cout << form("%3d ",playevals[i][j]);
- else
- cout << "*** ";
- cout << "\n";
- }
- }
- */
- return ((float)numwrong)*0.5/((float)numseen);
- }
- Opponent::~Opponent() {
- }
-
- // a PatternusingOpponent characterizes moves by some local
- // pattern on the board. When one is created it gets information
- // about which kind from the command line.
- PatternusingOpponent::PatternusingOpponent() {
- String s=nextarg();
- if (s=="nxn") {
- patterntouse=patnxn;
- getnum(nxnsize);
- }
- else if (s=="diamond") {
- patterntouse=patdiamond;
- getnum(mindiamondsize);
- getnum(maxdiamondsize);
- getnum(diamondmustsee);
- getnum(libscutoff);
- }
- else if (s=="group") {
- patterntouse=patgroup;
- getnum(groupradius);
- }
- else
- fatal("Bad pattern type specifier: "+s);
- }
- Pattern PatternusingOpponent::extract(Board& b, Move m) {
- Pattern p(patterntouse);
- p.extract(*this,b,m);
- return p;
- }
-
- // class MetaOpponent
- // a MetaOpponent is a convenience class which allows using the
- // sum of the evaluations of multiple other opponents for the
- // computation of error and gradient calculation. For example,
- // noise can be added by using the command line specification
- // "meta 2 random <opponent>", where 2 specifies that we will
- // be adding the evaluations of 2 opponents. By definition,
- // gradient info is copied to all sub-opponents equally.
- MetaOpponent::MetaOpponent() {
- getnum(numagents);
- agents=malloc(sizeof(Opponent*)*numagents);
- for (int i=0;i<numagents;i++)
- agents[i]=makeopponent();
- }
- MetaOpponent::~MetaOpponent() {
- for (int i=0;i<numagents;i++)
- delete agents[i];
- free(agents);
- }
- void MetaOpponent::evaluate(Board& b) {
- int i,j;
- doboard(i,j)
- if (b.islegalmove(Move(play,Pos(i,j)))) {
- playevals[i][j]=0.0;
- playable[i][j]=true;
- }
- else
- playable[i][j]=false;
- for (int k=0;k<numagents;k++) {
- agents[k]->evaluate(b);
- doboard(i,j)
- if (playable[i][j])
- playevals[i][j]+=agents[k]->playevals[i][j];
- }
- }
- void MetaOpponent::study(Board& b, Move m) {
- evaluate(b);
- int i,j;
- dontprint=true;
- for (int k=0;k<numagents;k++) {
- doboard(i,j)
- agents[k]->playevals[i][j]=playevals[i][j];
- agents[k]->study(b,m);
- }
- dontprint=false;
- takescore(b.movenum,score(m));
- }
-
- // class HashOpponent
- HashOpponent::HashOpponent() {
- getnum(hashsize);
- table=new float[hashsize];
- filename=nextarg();
- if (filename!="nofile") {
- FILE* fp=fopen(filename,"r");
- if (fp==NULL) {
- for (int i=0;i<hashsize;i++)
- table[i]=0.0;
- docheckpt();
- }
- else {
- fread(table,sizeof(float),hashsize,fp);
- fclose(fp);
- }
- }
- else
- for (int i=0;i<hashsize;i++)
- table[i]=0.0;
- }
- HashOpponent::~HashOpponent() {
- // delete [hashsize] table; g++ issues a warning on this
- delete table;
- }
- void HashOpponent::docheckpt() {
- if (filename!="nofile") {
- FILE* fp=fopen(filename,"w");
- fwrite(table,sizeof(float),hashsize,fp);
- fclose(fp);
- }
- }
- float HashOpponent::evalpos(Board& b, Pos p) {
- return table[extract(b,Move(play,p)).hash(hashsize)];
- }
- void HashOpponent::slide(Board& b, Pos p, float deriv) {
- table[extract(b,Move(play,p)).hash(hashsize)]+=deriv;
- }
-
- // class MapOpponent
- MapOpponent::MapOpponent():vals(0.0,49999) {
- filename=nextarg();
- if (filename!="nofile") {
- #ifdef IRIX
- fatal("Never rewrote MapOpponent::loading.");
- #endif // IRIX
- filebuf f;
- f.open(filename,input);
- istream from(&f);
- while (from.readable()) {
- String s;
- from >> s;
- if (s=="EOF")
- break;
- Pattern p(patterntouse,s);
- from >> vals[p];
- }
- }
- if (vals.contains(Pattern()))
- fatal("At initialization, contained null pattern.\n");
- }
- float MapOpponent::evalpos(Board& b, Pos p) {
- return vals[extract(b,Move(play,p))];
- }
- void MapOpponent::docheckpt() {
- if (filename!="nofile") {
- #ifdef IRIX
- fatal("Never rewrote MapOpponent::docheckpt.");
- #endif // IRIX
- File f(filename,io_writeonly,a_create);
- for (Pix i=vals.first();i!=0;vals.next(i)) {
- f.put(vals.key(i).str()+form(" %g\n",vals.contents(i)));
- }
- f.put("EOF\n");
- }
- }
- void MapOpponent::slide(Board& b, Pos p, float deriv) {
- vals[extract(b,Move(play,p))]+=deriv;
- }
-
- // class RandomOpponent
- float RandomOpponent::evalpos(Board& b, Pos p) {
- return rnd()*0.001;
- }
-
- // class GreedyOpponent
- float GreedyOpponent::evalpos(Board& b, Pos p) {
- Board bd(b);
- bd.applymove(Move(play,p));
- if (b.turntoplay==blackstone)
- return bd.whitetaken-b.whitetaken-(bd.blacktaken-b.blacktaken);
- else
- return bd.blacktaken-b.blacktaken-(bd.whitetaken-b.whitetaken);
- }
-
- // class CursesOpponent
- CursesOpponent::CursesOpponent() {
- w=new CursesWindow(stdscr);
- w->clear();
- crmode();
- noecho();
- }
- CursesOpponent::~CursesOpponent() {
- w->clear();
- w->refresh();
- delete w;
- }
- void CursesOpponent::evaluate(Board& b) {
- moveenum kind=play;
- w->erase();
- w->addstr(b.str());
- loop {
- w->move(19-row,2*col+4);
- w->refresh();
- char c=getchar();
-
- if (c==' ')
- if (b.islegalmove(Move(play,Pos(row,col))))
- break;
-
- if (c=='') {
- w->clear();
- w->addstr(b.str());
- w->refresh();
- continue;
- }
-
- if (c=='c') {
- w->move(22,0);
- w->addstr("Comment (press cntl-c when done):\n");
- w->refresh();
- echo();
- system((char*)("echo COM >>"+dribble));
- system((char*)("cat >>"+dribble));
- system((char*)("echo \" \" >>"+dribble));
- system((char*)("echo ENDCOM >>"+dribble));
- w->clear();
- w->addstr(b.str());
- w->refresh();
- noecho();
- continue;
- }
-
- if (c=='p') {
- kind=pass;
- break;
- }
-
- if (c=='q') {
- w->erase();
- w->refresh();
- exit(0);
- }
-
- if (c=='h'||c=='b'||c=='y') col--;
- if (c=='j'||c=='n'||c=='b') row--;
- if (c=='k'||c=='y'||c=='u') row++;
- if (c=='l'||c=='u'||c=='n') col++;
-
- if (col<0) col+=19;
- else if (col>18) col-=19;
- if (row<0) row+=19;
- else if (row>18) row-=19;
- }
-
- int i,j;
- doboard(i,j) {
- playevals[i][j]= -1.0;
- playable[i][j]=b.islegalmove(Move(play,Pos(i,j)));
- }
-
- if (kind==play) {
- playevals[row][col]=1.0;
- Board bd(b);
- bd.applymove(Move(play,Pos(row,col)));
- bd.comment="Thinking...";
- w->erase();
- w->addstr(bd.str());
- w->refresh();
- }
- }
-
- Opponent* makeopponent() {
- String s=nextarg();
- if (s=="curses")
- return(new CursesOpponent);
- else if (s=="random")
- return(new RandomOpponent);
- else if (s=="map")
- return(new MapOpponent);
- else if (s=="hash")
- return(new HashOpponent);
- else if (s=="greedy")
- return(new GreedyOpponent);
- else if (s=="meta")
- return(new MetaOpponent);
- else
- fatal("Bad opponent specifier.");
- }
-