views:

232

answers:

2

I am writing a train based game in Facebook Javascript. I have an inverted triangle that represents the train and dashed lines that represent track which connects cities in Europe. The train moves along the track to travel from one city to the next. At this time, I redraw the gameboard with every page refresh. This has the effect of always showing the train in its latest position on the track. However, using page refresh to redraw the gameboard is turning out to be too CPU intensive and my apps performance is suffering as a result. Hence I need to implement Ajax and some kind of sprite animation for the train.

When the train changes position on the track, I need to erase the train marker at its old position, and redraw it at it's new position. I know how to redraw it, but erasing it at it's old position and restoring the background graphics is where I need help. I have no idea how to do this. As a matter of fact, I have no knowledge of manipulating graphics in javascript at all (except a basic PlotPixel() routine that creates 1px divs) Please bear in mind that Facebook JS is incompatible with exisiting JS libraries and frameworks.

Can anyone offer me some help with this? I would very much appreciate it. Thanks!

Here's the relevant code:

function drawTrack(x1, y1, x2, y2, c, trains) {
if(c == '#444') new Dialog().showMessage('dialog', 'in drawTrack: x1='+x1+', y1='+y1+', x2='+x2+', y2='+y2);
/**
if(trains[0]) {
var train_dump = dump(trains[0], 2);
new Dialog().showMessage('dialog', 'td='+train_dump);
}
**/
    var xs1 = x1;
    var ys1 = y1;
    var xs2 = x2;
    var ys2 = y2;
    var step = 1;
    var dashlen = 4;
    var middash = parseInt(dashlen/2);
    var count = 1;
    var steep = Math.abs(y2 - y1) > Math.abs(x2 - x1);
    if (steep) {
        var t = y1;
        y1 = x1;
        x1 = t;
        t = y2;
        y2 = x2;
        x2 = t;
    }
    var deltaX = Math.abs(x2 - x1);
    var deltaY = Math.abs(y2 - y1);
    var error = 0;
    var deltaErr = deltaY;
    var xStep;
    var yStep;
    var x = x1;
    var y = y1;
    var drewDash;
    if (x1 < x2) {
        xStep = step;
    }
    else {
        xStep = -step;
    }

    if(y1 < y2) {
        yStep = step;
    }

    else {
        yStep = -step;
    }
    var points = 0;
    var drawFlag = false;
    while(x != x2) {
        x = x + xStep;
        if(xStep > 0) {
          if(x > x2) {
            break;
          }
          // do not draw dashes near the city markers
          if(x >= x2-dashlen) {
            drawFlag = false;
          }
        }
        if(xStep < 0) {
          if(x < x2) {
            break;
          }
          // do not draw dashes near the city markers
          if(x <= x2+dashlen) {
            drawFlag = false;
          }
        }
        error = error + deltaErr;
        if(2 * error >= deltaX) {
            y = y + yStep;
            error = error - deltaX;
        }
        if(points < dashlen) {
          if(drawFlag) {
            if(steep) {
                PlotPixel(y, x, c);
                drewDash = true;
            }
            else {
                PlotPixel(x, y, c);
                drewDash = true;
            }
            if(points == middash) {
              if(trains[0]) {
                for(var key = 0; key < trains.length; key++) {
                  if(trains[key]['track_unit'] == count) {
                    if(steep) {
                      trainMarker(y, x, trains[key]);
                    } else {
                      trainMarker(x, y, trains[key]);
                    }
                  }
                }
              }
            }
          }
          points++;
       } else {
          drawFlag = !drawFlag;
          if(drewDash) count++;
          points = 0;
          drewDash = false;
       }
    }

    if(trains[0]) {
      for(var key = 0; key < trains.length; key++) {
        if(trains[key]['status'] == 'ARRIVED') {
          if(trains[key]['direction'] == '+') {
            trainMarker(xs2, ys2, trains[key]);
          } else {
            trainMarker(xs1, ys1, trains[key]);
          }
        }
      }
    }
    return count;
}

function trainMarker(x, y, trainData) {
  var train = document.createElement('div');
  train.setClassName('train');
  y -= trainMarkerHeight;
  x -= parseInt(trainMarkerWidth/2);
  var trainId = 'train-'+trainData['line'].toLowerCase()+'-'+trainData['player_number'];
  train.setId(trainId);
  train.setStyle('left', x + 'px');
  train.setStyle('top', y + 'px');

  var trainImg = document.createElement('img');
  trainImg.setSrc(publicURL + '/images/train_marker_red.gif');
  train.appendChild(trainImg);

  var trainPlayerNum = document.createElement('div');
  trainPlayerNum.setClassName('train-player-num');
  trainPlayerNum.setId('train-player-'+trainData['player_number']);
  trainPlayerNum.setTextValue(trainData['player_number']);
  trainPlayerNum.setStyle('left', '8px');
  trainPlayerNum.setStyle('top', '1px');
  trainPlayerNum.addEventListener('mouseover', myEventHandler);
  trainPlayerNum.addEventListener('mouseout', myEventHandler);
  trainPlayerNum.appendChild(setTrainTooltip(trainData));
  train.appendChild(trainPlayerNum);

  var parentObj = document.getElementById('map');
  parentObj.appendChild(train);
}

This code draws track (dashed lines) from one city's x,y coords to another city's x,y coords. If a train is present on that line of track, it draws the train as well. If the train is present at one of the cites, it draws the train at the city.

The game itself is at http://apps.facebook.com/rails%5Fdev.

+2  A: 

Perhaps instead of manipulating the image itself, you can move the train image over top of the position (map image?) using z-index and an absolutely positioned div? This might be easier to implement and more performant than trying to create or modify the actual image data in javascript.

[Edit: to show or hide an image, the javascript looks like:

(given some HTML like:)

<img src="train.jpg" id="trainImg" />
<img src="background.jpg" id="backgroundImg" />

The javascript looks like:

function getTrain() {
  document.getElementById("trainImg");
}

function showTrain() {
  getTrain().style.display = "";
}

function hideTrain() {
  getTrain().style.display = "none";
}

]

RMorrisey
Probably don't even need to play with z-index of absolute positioning. For a more user friendly experience you could have the train constantly within the center of the div, thus only needing to move the background (the cities and tracks, if you will) with a simple CSS scroll.
jakeisonline
Please see my code above and help me understand how your suggestions might apply to the code. Thank you.
Chris Barnhill
If I take my train div and call removeChild() on it, will that get rid of it and restore the background?
Chris Barnhill
I'm having a little trouble following exactly what your code does, but I see that it's an algorithm to draw each pixel individually. My suggestion was, instead of drawing, to use pre-rendered images using the <img> tag. You can move them around by setting the CSS properties in javascript, and use display: none to hide them when you want them not to be shown. Using display: none will make the element invisible, but if you have to show it again it will not need to be reloaded. levik's example, below, has the javascript code for setting the style position attributes
RMorrisey
Edited my response to show javascript code for showing/hiding the train <img> element
RMorrisey
+1  A: 

Definitely turn your "game world" into a div with the map and track on the background image. You can then manipulate an <img> element (your train) to change its location:

var train = document.createElement("img");
train.srx = "...";
train.style.position = "absolute";
document.getElementById("gameboard").appendChild(train);    

function moveTrainTo(x, y) {
  train.style.left = x + "px";
  train.style.top = y + "px";
}
levik