  // Constants
  var RI_TILE_SIZE = 55;
  var RI_WALL_THICKNESS = 6;
  var RI_WALL_LENGTH = 60;
  var RI_JOINT_SIZE = 12;
  var RI_MAX_ROBOTS = 6;
  var RI_IMAGE_BASE = "ricochetrobot/";

  // These holds references to all of the games that have been created
  var riGames = new Array();

  // RiGame Constructor
  function RiGame(initSize, initRobots, initGoals, initWalls)
  {
    var tmp,tmp2;

    // Add this game to the game array
    this.riGameIdx = riGames.length;
    riGames[riGames.length] = this;    

    // Undo Array
    this.undos = new Array();
    this.undoIdx = 0;

    // Width & Height
    tmp = initSize.split(",");
    this.width = tmp[0];
    this.height = tmp[1];

    // Robots
    tmp = initRobots.split("|");
    this.robots = new Array();
    for (var i=0; i<tmp.length && i<RI_MAX_ROBOTS; i++) {
      tmp2 = tmp[i].split(",");
      this.robots[i] = parseInt(tmp2[0]-1) + (this.width * (tmp2[1]-1));
    }

    // Robot starting positions
    this.startRobots = new Array(this.robots.length);
    for (var i=0; i<this.robots.length && i<RI_MAX_ROBOTS; i++) {
      this.startRobots[i] = this.robots[i];
    }
   
    // Goals
    tmp = initGoals.split("|");
    this.goals = new Array();
    for (var i=0; i<tmp.length; i++) {
      if (tmp[i] == 'x' || tmp[i] == '') {
        this.goals[i] = -1;
      } else {
        tmp2 = tmp[i].split(",");
        this.goals[i] = parseInt(tmp2[0]-1) + (this.width * (tmp2[1]-1));
      }
    }

    // Walls
    tmp = initWalls.split("|");
    this.walls = new Array();
    for (var i=0,j=0,x,y,d,k; i<tmp.length; i++) {
      d = tmp[i].substring(0,1);
      tmp2 = tmp[i].substring(1).split(",");
      x = tmp2[0]-1;
      y = tmp2[1]-1;
      k = x + (2*this.width*y);
      if (d == 'N')  k -= this.width;
      else if (d == 'S')  k += parseInt(this.width);
      else if (d == 'W')  k -= 1;
      
      if ((x <= 0 && d == 'W')
       || (x >= (this.width-1) && d == 'E')
       || (y <= 0 && d == 'N')
       || (y >= (this.height-1) && d == 'S'))  k = -1;
      
      if (k >= 0)  this.walls[j++] = k;
    }

    // Declare member functions
    this.drawBoard = RiGame_drawBoard;
    this.robot_OnMouseOver = RiGame_robot_OnMouseOver;
    this.OnMouseMove = RiGame_OnMouseMove;
    this.OnClick = RiGame_OnClick;
    this.undo = RiGame_undo;
    this.restart = RiGame_restart;
    this.victory = RiGame_victory;
  }

  // Draw the board
  function RiGame_drawBoard()
  {
    // Localize the member variables
    var robots = this.robots;
    var width = this.width;
    var height = this.height;
    var goals = this.goals;
    var walls = this.walls;

    document.writeln(
      "<TABLE Cellspacing=0 Cellpadding=2><TR><TD Style='font-size:12px;'>" + 
      "<INPUT Type=button Value='Restart' OnClick='riGames["+this.riGameIdx+"].restart();' Style='font-size:8px;'>&nbsp;&nbsp;" + 
      "<INPUT ID='riUndoButton["+this.riGameIdx+"]' OnClick='riGames["+this.riGameIdx+"].undo();' Type=button Value='Undo' Style='font-size:8px;' disabled>&nbsp;&nbsp;" +
      "Move #<INPUT ReadOnly ID='riMoveCount["+this.riGameIdx+"]' Type=text Value='0' Size=3 Style='font-size:12px;border:0pt;padding:0pt;'>" +
      "</TD></TR><TR><TD>"
    );

    // Make the main board layer
    riDiv(null, "ri_tile.png", (width*RI_TILE_SIZE), (height*RI_TILE_SIZE), 0, 0, "border:1pt outset;", "", "relative", true);

    // Goals
    for (var i=0; i<goals.length; i++) {
      if (goals[i] == -1)  continue;
      riDiv(null, "ri_goal"+i+".png", RI_TILE_SIZE, RI_TILE_SIZE, (Math.floor(goals[i]/width)*RI_TILE_SIZE), ((goals[i]%width)*RI_TILE_SIZE));
    }

    // Movement Arrow
    riDiv("riArrow["+this.riGameIdx+"]", "ri_blanktile.png", RI_TILE_SIZE, RI_TILE_SIZE, 0, 0, "", "OnMouseMove='riGames[" + this.riGameIdx + "].OnMouseMove(event);' OnClick='riGames[" + this.riGameIdx + "].OnClick(event);'");
    
    // Robots
    for (var j=0; j<this.robots.length; j++) {
      x = this.robots[j]%width;
      y = Math.floor(robots[j]/width); 
      riDiv("riRobot["+this.riGameIdx+"]["+j+"]", "ri_robot"+j+".png", RI_TILE_SIZE, RI_TILE_SIZE, (y*RI_TILE_SIZE), (x*RI_TILE_SIZE), 
            "", "OnClick='riGames[" + this.riGameIdx + "].OnClick(event);' OnMouseMove='riGames[" + this.riGameIdx + "].OnMouseMove(event);' OnMouseOver='riGames[" + this.riGameIdx + "].robot_OnMouseOver(event,this,"+j+");'");
    }
     
    // Walls
    for (var i=0,j=0,k=0; i<walls.length; i++) {
      j = walls[i]%(width*2);
      k = Math.floor(walls[i]/(width*2));

       if (j < width) {
         riDiv(null, "ri_vwall.png", RI_WALL_THICKNESS, RI_WALL_LENGTH, 
               (k*RI_TILE_SIZE-(RI_WALL_THICKNESS/2)), (((j+1)*RI_TILE_SIZE)-(RI_WALL_THICKNESS/2)));
       } else {
	 riDiv(null, "ri_hwall.png", RI_WALL_LENGTH, RI_WALL_THICKNESS,
               (((k+1)*RI_TILE_SIZE)-(RI_WALL_THICKNESS/2)), ((j-width)*RI_TILE_SIZE-(RI_WALL_THICKNESS/2)));
       }
     }

     // Joints
     for (var i=0,n=0; i<walls.length; i++) {
       if (walls[i]%(width*2) < width) {
         for (var j=0,k=-1; j<walls.length; j++,k=-1) {
	   n = walls[j] - walls[i];
	   if ( n == (-width*2) || n == (1-width))  k = 0;
	   else if (n == (parseInt(width)+1))  k = 1;
	   if (k != -1) {
             riDiv(null, "ri_joint.png", RI_JOINT_SIZE, RI_JOINT_SIZE, ((Math.floor(walls[i]/(width*2))+k)*RI_TILE_SIZE-(RI_JOINT_SIZE/2)),
                   ((walls[i]%(width*2)+1)*RI_TILE_SIZE-(RI_JOINT_SIZE/2)));
	   }
         }
       } else {
         for (var j=0,k=-1; j<walls.length; j++,k=-1) {
	   n = walls[j] - walls[i];
	   if ( n == (-width) || n == 1 || n == (width) )  k = 0;
	   if (k != -1) {
             riDiv(null, "ri_joint.png", RI_JOINT_SIZE, RI_JOINT_SIZE, 
	           (((Math.floor(walls[i]/(width*2))+1)*RI_TILE_SIZE)-(RI_JOINT_SIZE/2)), 
		   (((walls[i]%(width*2))-width+1)*RI_TILE_SIZE-(RI_JOINT_SIZE/2)));
	   }
	 }
       }
     }
  
     // Close the board
     document.writeln("</TD></TR></TABLE></DIV>");

     // Grab the references to a few objects
     this.undoButton = riFindObj("riUndoButton[" + this.riGameIdx + "]");
     this.moveCount = riFindObj("riMoveCount[" + this.riGameIdx + "]");
     this.arrow = riFindObj("riArrow[" + this.riGameIdx + "]");
   }
   
   // Handle Undo Button Click
   function RiGame_undo() 
   {
     var robot, robotID, loc;
   
     if (this.undoIdx > 0) {
       this.undoIdx-=2;
       robotID = this.undos[this.undoIdx];
       loc = this.undos[this.undoIdx+1];
       
       robot = riFindObj("riRobot[" + this.riGameIdx + "][" + robotID + "]");
       robot.style.top = Math.floor(loc/this.width) * RI_TILE_SIZE;
       robot.style.left = (loc%this.width) * RI_TILE_SIZE;
       this.robots[robotID] = loc;
     }
     if (this.undoIdx == 0)  this.undoButton.disabled = true;
     this.moveCount.value = (this.undoIdx/2);
     
     this.arrow.style.background = "url(" + RI_IMAGE_BASE + "ri_blanktile.png)";
     this.arrow_loc = -1;
   }
   
   // Restart the game
   function RiGame_restart()
   {
     var robot;
   
     this.undoIdx = 0;
     this.undoButton.disabled = true;
     this.moveCount.value = "0";

     for (var i=0; i<this.robots.length; i++) {
       this.robots[i] = this.startRobots[i];
       robot = riFindObj("riRobot[" + this.riGameIdx + "][" + i + "]");
       robot.style.top = Math.floor(this.robots[i]/this.width) * RI_TILE_SIZE;
       robot.style.left = (this.robots[i]%this.width) * RI_TILE_SIZE;
     }
   }
   
   // Move the arrow to the correct square
   function RiGame_robot_OnMouseOver(event, robot, robotID)
   {
     if (this.arrow_loc != this.robots[robotID]) {
       this.arrow_loc = this.robots[robotID];
       this.arrow.style.top = Math.floor(this.arrow_loc/this.width) * RI_TILE_SIZE;
       this.arrow.style.left = (this.arrow_loc%this.width) * RI_TILE_SIZE;
       this.robotID = robotID;
     }
   }
   
   // Change the arrow direction
   function RiGame_OnMouseMove(event)
   {
     var xdiff,ydiff,dir,x,y;

     if (this.arrow_loc == -1) {
       this.robot_OnMouseOver(event, riFindObj("riRobot[" + this.riGameIdx + "][" + this.robotID + "]"), this.robotID);
     }

     x = (event.offsetX != null ? event.offsetX : event.layerX);
     y = (event.offsetY != null ? event.offsetY : event.layerY);
     xdiff = x - (RI_TILE_SIZE/2);
     ydiff = y - (RI_TILE_SIZE/2);
     
     if (Math.abs(xdiff) > Math.abs(ydiff)) {
       if (xdiff > 0)  dir = 'e';
       else  dir = 'w';
     } else {
       if (ydiff > 0)  dir = 's';
       else  dir = 'n';
     }

     if (this.arrow_dir != dir) {
       this.arrow_dir = dir;
       this.arrow.style.background = "url(" + RI_IMAGE_BASE + "ri_" + dir + "arrow.png)";
     }
   }
   
   // Handle Mouse Click
   function RiGame_OnClick(event) 
   {
     var loc, mod=0, goalCount=0;
     var robot, robotID;

     if (this.arrow_loc == -1)  return;

     // Localize member variables
     var width = this.width;
     var height = this.height;
     var robots = this.robots;
     var walls = this.walls;
     var wall;

      // Get the selected robot
      robotID = this.robotID;
      robot = riFindObj("riRobot[" + this.riGameIdx + "][" + robotID + "]");

      // Add this move to the undo list
      this.undoButton.disabled = false;
      this.undos[this.undoIdx++] = robotID;
      this.undos[this.undoIdx++] = robots[robotID];
      this.moveCount.value = (this.undoIdx/2);

      // Up and down
      switch (this.arrow_dir) {
        case 'n': mod = -width; break;
	case 'e': mod = 1; break;
	case 's': mod = width; break;
	case 'w': mod = -1; break;
      }
      
      // Move the robot
        for (var j=0; j<width; j++) {
          loc = robots[robotID] + parseInt(mod);

	  for (var i=0; i<robots.length; i++) {
	    if (loc == robots[i]) {
	      loc = -1;
	      break;
	    }
	  }
	  wall = ((Math.floor(robots[robotID]/width)*width*2)+(robots[robotID]%width)+parseInt(mod));
	  if (mod == 1)  wall--;
	  for (var i=0; i<walls.length; i++) {
	    if (walls[i] == wall) { loc = -1; break; }
	  }
	  if (loc <= -1 || loc >= (width*height))  break;
	  if (Math.abs(mod) == 1 && (Math.floor(robots[robotID]/width) != Math.floor(loc/width)))  break;
	  robots[robotID] = loc;
	}
       
	robot.style.top = Math.floor(robots[robotID]/width) * RI_TILE_SIZE;
	robot.style.left = (robots[robotID]%width) * RI_TILE_SIZE;
        if (this.arrow_loc != robots[robotID]) {
	  this.arrow.style.background = "url(" + RI_IMAGE_BASE + "ri_blanktile.png)";
	  this.arrow_loc = -1;
	}

	// Check for victory
	for (var i=0; i<this.goals.length; i++) {
	  if (this.goals[i] == -1 || this.goals[i] == this.robots[i])
	    goalCount++;
	  else
	    break;
	}
	if (goalCount == this.goals.length)  this.victory();
   }

   // When the player wins
   function RiGame_victory()
   {
     alert("Congratulations!  You have completed this puzzle in " + (this.undoIdx/2) + " turn" + (this.undoIdx > 2 ? "s" : "") + ".");
   }

   // Draw a DIV tag
   function riDiv(id, bkg, width, height, top, left, other_style, other, position, leave_open) 
   {
     if (other == null)  other = '';
     if (other_style == null)  other = '';
     if (position == null)  position = 'absolute';
     document.writeln("<DIV " + (id != null ? "ID='" + id + "' " : "") + "Style='" + (leave_open ? "background:url(" + RI_IMAGE_BASE + bkg + ");" : "") + "position:" + position + ";width:" + width + ";height:" + height + ";top:" + top + ";left:" + left + ";" + other_style + ";' " + other + ">" + (leave_open ? "" : "<IMG Src='" + RI_IMAGE_BASE + bkg + "'></DIV>"));

//     document.writeln("<DIV " + (id != null ? "ID='" + id + "' " : "") + "Style='background:url(" + RI_IMAGE_BASE + bkg + ");position:" + position + ";width:" + width + ";height:" + height + ";top:" + top + ";left:" + left + ";" + other_style + ";' " + other + ">" + (leave_open ? "" : "</DIV>"));
   }

   // Find an object (platform independent)
   function riFindObj(n, d) 
   {
     var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
      d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
     if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
     for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document);
     if(!x && d.getElementById) x=d.getElementById(n); return x;
   }

