1

I'm working on an implementation of tetris to teach myself javascript and I've run into a problem. First, the background: I have a class for each type of tetris block that inherits from a basic block template with methods like moving and returning coordinates. For instance,

function BlockTemplate(w, h, c){
    var height = h;
    var width = w;
    var x = 0;
    var y = 0;
    var color = c;

    this.getHeight = function(){return height;};
    this.getWidth = function(){return width;};
    this.getX = function(){return x;};
    this.getY = function(){return y;};
    this.getColor = function(){return color;};
    this.moveTo = function(newX, newY){
        x = newX;
        y = newY;
    };
    this.translate = function(dx, dy){
        x += dx;
        y += dy;
    };
}

function StraightBlock(){
    this.draw = function(){
        ctx.fillStyle = this.getColor();
        ctx.fillRect(this.getX(), this.getY(), 20, 100);
    };
}
StraightBlock.prototype = new BlockTemplate(20, 100, "#00FFE5");

All blocks currently on the screen are stored in an array, blockArr, except for the currently falling block, which is stored in curBlock.

I use a function called createRandomBlock() to create a block and put it in curBlock:

var blockTypeArr = [LBlock, SquareBlock, StraightBlock, SquigBlock];

var createRandomBlock = function(){
    var block = blockTypeArr[Math.floor(Math.random()*blockTypeArr.length)];
    var randomBlock = new block();
    return randomBlock;
};

curBlock = createRandomBlock();

Once it's done falling I put it in the array and create a new block:

blockArr[blockArr.length] = curBlock;
curBlock = createRandomBlock();

If the newly created block hasn't yet been on screen then there's no problem. However, using the moveTo and translate methods of the new instance affect all instances of that class (I added an id property to make sure they were in fact distinct instances).

For example, using the JavaScript console,

>curBlock
  SquigBlock
>blockArr
  [SquareBlock, SquigBlock, SquigBlock]

As you can see, 3 SquigBlocks have fallen so far (2 in blockArr, 1 presently falling). Yet the only one I see is the one currently falling (curBlock), and checking the parameters, curBlock.getY(), blockArr[1].getY() and blockArr[2].getY() all return the same value. In other words, they're all being drawn in the same location.

How can I change it so that old blocks, no matter what the class, stay at the bottom of the screen, while the newly created block falls from the top without causing all other blocks of that class to move with it?

Thank you!

tytk
  • 2,082
  • 3
  • 27
  • 39
  • possible duplicate of [Why are my JavaScript object properties being overwritten by other instances?](http://stackoverflow.com/questions/13127589/why-are-my-javascript-object-properties-being-overwritten-by-other-instances) – Bergi Jan 30 '14 at 00:29

2 Answers2

1
StraightBlock.prototype = new BlockTemplate(20, 100, "#00FFE5");

Well, that is only one BlockTemplate that is shared amongst with StraightBlock instances. You can see that new StraightBlock().moveTo == new StraightBlock().moveTo, ie two instances have the very same method which does affect the same x variable. Do not use new for creating the prototype, but Correct javascript inheritance:

function StraightBlock(){
    BlockTemplate.call(this, 20, 100, "#00FFE5");
    this.draw = function(){
        ctx.fillStyle = this.getColor();
        ctx.fillRect(this.getX(), this.getY(), 20, 100);
    };
}
StraightBlock.prototype = Object.create(BlockTemplate.prototype);
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Brilliant! Does Object.create just create a copy of the BlockTemplate prototype? – tytk Jan 30 '14 at 06:08
  • Exactly, if by "copy" you focus on the fact that it does not invoke the constructor there. However, it does not "copy" properties, but [creates an empty object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) that does inherit from BlockTemplate prototype. – Bergi Jan 30 '14 at 13:35
1
StraightBlock.prototype = new BlockTemplate(20, 100, "#00FFE5");

Since this is called once, there is one var x that is closed over by the single getHeight that is shared among all instances of StraightBlock.

You should make two changes:

1 Use instance fields stored on this

function BlockTemplate(w, h, c){
   this.height = h;
   ...
}

BlockTemplate.prototype.getHeight = function(){return this.height;};
...

2 Chain constructors

Have StraightBlock invoke its super-class constructor to add new versions of the methods to each instance created.

function StraightBlock(){
  BlockTemplate.apply(this, arguments);
  ...
}

You could also just do constructor chaining, but that would mean that you are creating a new copy of each method for each instance. This can be expensive if you have a lot of short-lived instances of a "class".

The drawback of storing fields on this is that they can be unintentionally mutated by naive code. JavaScript does not have strong information hiding except via closed-over local variables, so if you want the robustness that you get via private fields in other languages, then your current design via local variables might be best.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • The first change is hardly necessary, and makes the getter methods superfluous. – Bergi Jan 30 '14 at 00:28
  • @Bergi, If you're using something like Closure Compiler then getters and setters are aggressively inlined, and you get some level of protection against unintended mutation. – Mike Samuel Jan 30 '14 at 00:31
  • Ahh, I see now where my logic failed. Does BlockTemplate.apply(this, args) do the same thing as StraightBlock.prototype = Object.create(BlockTemplate.prototype)? Or is there a difference? – tytk Jan 30 '14 at 06:11
  • `Object.create(...)` creates a blank object with no properties. `BlockTemplate.apply(this, args)` uses the object created by the call to `new StraightBlock` and runs the body of `BlockTemplate` to set properties on it. – Mike Samuel Jan 30 '14 at 16:31