// Photon.C -- written 16 Mar 1996 by Max Hailperin // // This is the implementation of the Photon class, which is the heart of the // Photon game. See Photon.h for more about its responsibilities. #include "Photon.h" #include "Obstacle.h" #include "Ball.h" #include #include #include #include #include #include #include #include static char *copyStr(const char *str){ char *result = new char[strlen(str)+1]; assert(result); // lazy check for out of memory strcpy(result, str); return result; } // When the TCL script executes the photon command to make a new game, // i.e., a new Photon object, this is the procedure that is run. // It creates the new Photon and installs it as a TCL command of its // own. The two argv arguments from TCL are the name that new command should // have, and the name of the canvas to draw on. int Photon::createCmd(ClientData, Tcl_Interp *theInterp, int argc, char *argv[]){ if(argc != 3){ theInterp->result = "wrong # args"; return TCL_ERROR; } static int firstTime = 1; if(firstTime){ firstTime = 0; srandom((unsigned int)time(NULL)); // seed pseudo-random number generator } Photon *p = new Photon(theInterp, copyStr(argv[2])); assert(p); // lazy check for out of memory if(p->tclCode != TCL_OK) // tclCode gets set in the constructor return p->tclCode; Tcl_CreateCommand(theInterp, argv[1], cmd, (ClientData) p, deleteProc); return TCL_OK; } Photon::Photon(Tcl_Interp *theInterp, char *canvas) : interp(theInterp), cnvs(canvas) { // first order of business is getting the canvas's size in pixels if((tclCode = draw("cget -width")) != TCL_OK) return; if((tclCode = Tcl_GetInt(interp, interp->result, &fieldWidthPixels)) != TCL_OK) return; if((tclCode = draw("cget -height")) != TCL_OK) return; if((tclCode = Tcl_GetInt(interp, interp->result, &fieldHeightPixels)) != TCL_OK) return; // now we can clear out the playing field, then put the Obstacles in int x, y; for(x = 0; x < fieldWidth; x++) for(y = 0; y < fieldHeight; y++) field[x][y] = 0; // horizontal walls top and bottom for(x = 1; x < fieldWidth-1; x++){ field[x][0] = new HWall(this, x, 0); field[x][fieldHeight-1] = new HWall(this, x, fieldHeight-1); } // vertical walls on the sides for(y = 1; y < fieldHeight-1; y++){ field[0][y] = new VWall(this, 0, y); field[fieldWidth-1][y] = new VWall(this, fieldWidth-1, y); } // corners in the corners (where else!) field[0][0] = new LLCorner(this, 0,0); field[0][fieldHeight-1] = new ULCorner(this, 0, fieldHeight-1); field[fieldWidth-1][0] = new LRCorner(this, fieldWidth-1,0); field[fieldWidth-1][fieldHeight-1] = new URCorner(this, fieldWidth-1,fieldHeight-1); // the ball starts at position (1,7), headed in the positive x direction ball = new Ball(this, 1, 7, 1, 0); pickTargetLocation(x, y); field[x][y] = new Target(this, x, y); Tcl_ResetResult(interp); } int Photon::draw(const char *scriptTail){ // ask canvas to draw something ostrstream strm; strm << canvas() << ' ' << scriptTail << ends; char *script = strm.str(); int result = eval(script); delete [] script; return result; } // The following cmd procedure is what is used when the TCL script uses the // specific Photon game's command to do a time step worth of action; the // ClientData is used to pass in a pointer to the particular Photon object // that should be used, while the argv[1] is a string representing which // command the user is currently doing (i.e., most recently done since last // time step). It can be none, neg, pos, or erase. This is translated // to an appropriate enumeration constant for the particular Photon's // doTimeStep member function, which does the real work. int Photon::cmd(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]){ Photon *thePhoton = (Photon *) clientData; assert(interp == thePhoton->interpreter()); if(argc != 2){ interp->result = "wrong # args"; return TCL_ERROR; } if(strcmp(argv[1], "none") == 0) return thePhoton->doTimeStep(none); else if(strcmp(argv[1], "neg") == 0) return thePhoton->doTimeStep(neg); else if(strcmp(argv[1], "pos") == 0) return thePhoton->doTimeStep(pos); else if(strcmp(argv[1], "erase") == 0) return thePhoton->doTimeStep(erase); else{ interp->result = "illegal photon option: must be none, neg, pos, or erase"; return TCL_ERROR; } } // The doTimeStep member function plays out one unit of time worth of action: // - if the ball is in an empty cell and the user has asked for a negative // slope mirror or positive slope mirror, give them one // - if the ball is in a cell occupied by an erasable Obstacle, and the // user asked to erase it, do so // - let the Obstacle in the cell if any (after the above two steps) be // hit by the ball, possibly doing interesting things and in any case // returning a score increment // - finally, tell the ball to move one cell in the direction it is headed int Photon::doTimeStep(Command c){ int x = ball->x(), y = ball->y(); if(!field[x][y]) switch(c){ case neg: field[x][y] = new NMirror(this, x, y); break; case pos: field[x][y] = new PMirror(this, x, y); break; } else if(c == erase && field[x][y]->erasable()){ delete field[x][y]; field[x][y] = 0; } int scoreChange; if(field[x][y]){ scoreChange = field[x][y]->beHitBy(ball); } else scoreChange = 0; ball->move(); ostrstream strm(interpreter()->result, TCL_RESULT_SIZE); strm << scoreChange << ends; return TCL_OK; } // Deleting one of the Photon-object specific commands in TCL deletes the // underlying Photon object itself. void Photon::deleteProc(ClientData clientData){ delete (Photon *) clientData; } // Deleting a Photon game deletes all the stuff (which also erases the canvas). Photon::~Photon(){ delete ball; for(int x = 0; x < fieldWidth; x++) for(int y = 0; y < fieldHeight; y++) delete field[x][y]; delete [] cnvs; } // Warning: the below procedure to pick a random unoccupied location *could* // loop forever. Hopefully the field will never get so crowded as to make this // likely. It might be better to put in a loop counter, and when it gets real // big abort. void Photon::pickTargetLocation(int &x, int &y){ do{ x = int(random() % fieldWidth); y = int(random() % fieldHeight); }while(!okForTarget(x, y)); } int Photon::okForTarget(int x, int y){ return !field[x][y] && (ball->x() != x || ball->y() != y); } void Photon::moveObstacle(int oldX, int oldY, int newX, int newY){ assert(!field[newX][newY]); field[newX][newY] = field[oldX][oldY]; field[oldX][oldY] = 0; } int Photon::moveCanvasItem(int id, int oldX, int oldY, int newX, int newY){ char s[128]; ostrstream strm(s, 128); strm << "move " << id << ' ' << mapX(newX) - mapX(oldX) << ' ' << mapY(newY) - mapY(oldY) << ends; return draw(s); } int Photon::deleteCanvasItem(int id){ char s[128]; ostrstream strm(s, 128); strm << "delete " << id << ends; return draw(s); }