You are reading a single comment by @allObjects and its replies. Click here to read the full conversation.
  • #game #tetris

    Had the chance to look at the code.... I guess I understand a bit what you do...

    Did you consider to make each block a class? If you do so, you do not need switching...

    Think about each block being a thing that understands the following 'calls' or actions:

    1. f1() - fall one step (+1 along y-axis)
    2. fA() - fall all the way through
    3. m(d) - move in +/-1 d(irection) along x-axis
    4. r(d) - rotate in -/+1 d(irection) = counter/clock-wise 90 degrees
    5. dr(c) - draw with color c

    Until now, we defined all the so called behavior - the actions a block can do when asked by itself or by 'others'. There will be more of those, but for now let it be with the the above 5.

    Next we need to define what each block needs to know about itself (and others can ask the block about) - the so called state of the block:

    1. bc - block color
    2. sc - score points for block
    3. x - x coord on the board ((extended) board top left corner is x=0,y=0, top right is 10,0)
    4. y - y coord on the board
    5. ps - points (squares) - array of [x,y] arrays of points relative to x and y the block occupies
    6. rx - rotation index in the set of possible rotations
    7. rs - rotations, an array of ps representing all rotations

    So let me give you an example for the simples block - the 'square-1':

    // used to temporarily hold on to prototype
    var p; 
    
    // square-1 block class definition 
    //
    // The class definition begins with defining a function, the so 
    // called constructor that will be invoked with new and sets 
    // everything that an instance will have unique (state). Since 
    // certain values - like the color,  score, etc. is all the same 
    // for a particular block, they are not included in the 
    // constructor, rather defined only on the prototype like all
    // the actions of the block. The constructor for blocks where 
    // the rotational position matters, an initial random rotation 
    // index rx is passed to pick and set the initial points and 
    // as well the current rotation index for the block. Since those 
    // do not matter for the SQ1, they are not mentioned here... For 
    // the generic block creation algorithm in the board though, the 
    // possible rotations - exactly one - have (has?) to be defined.
    // For example, see the code in the attached html, which 
    // includes the B2 block - bar with 2 points - where rotation
    // matters.
    var SQ1 = function() {
      this.x  = 4; 
      this.y  = 1;
      this.ps = [[0,0]];
    }
    
    // shortening the prototype assignments 
    p = sq1.prototype;
    
    // shared block color, score, and all the possible rotations 
    // are defined once now including all the behavior (methods).
    // color is either a single number or an rgb-compound.
    p.bc = 1;
    p.sc = 1;
    p.rs = [[[0,0]]];
    
    // draw method of  sq1:
    // general block drawing is going through the points like 
    // this:
    //   this.ps.forEach(function(p){ 
    //     g.setPixel(this.x+p[0],this.y+p[1],this.­c);
    //   });
    // but for SQ1 this can be simplified as done below.
    // g is 'global' var for graphics you connect/talk to
    p.dr = function(c) { g.setPixel(this.x,this.y,c); };
    
    // fall 1 down (triggered by the timer thought the board... 
    // see board code. When it cannot fall down anymore, the 
    // block becomes frozen and a next block is created... all 
    // done through the board... see board code. To make that 
    // happen, the block checks new position and if free, 
    // returns true - and of course, 'moves' to the new position 
    // by un-draw at current position with background color and 
    // re-draw in new color. If new position is not free, nothing 
    // needs to be done, because by default a function returns 
    // undefined and that is interpreted by the board as false 
    // (could not 'fall down'), and the block becomes frozen.
    p.f1 = function() {
      if (brd.f(this.x,this.y,0,1,this.ps)) {
        this.dr(0);
        this.y++;
        this.dr(this.bc);
        return true;
      }
    };
    
    // for SQ1 rotate does not matter, therefore, it is an empty 
    // function (it is way 'cheaper' to have this empty function
    // to have a generic invocation in the board on rotation
    // request that figuring out for which blocks it matters
    // and have such a function and then invoke it...
    p.r = function(d){};
    
    // move left and right do something... first check with board
    // if new place free. If fee, the block moves there by first 
    // 'un-drawing' with background color bg on current position 
    // and then re-drawing with block color bc on new position.
    // Background color is some 0-ish value.
    p.m = function(d) {
      if (brd.f(this.x,this.y,d,0,this.ps))  {
        this.dr(0);
        this.x += d;
        this.dr(this.bc);
     }
    };
    
    // as last thing we register the block class with the board.
    // This will all the board to pick randomly the next block
    // block from the list of possible blocks and also set the
    // initial rotation position randomly... (which of course 
    // for the SQ1 does not matter). 
    // see later in code. 
    
    brd.rBC(SQ1);
    

    As I remember from playing, you have only one piece active - moving - at a time; and the pieces that have settled do not matter anymore, because when they settle they occupy a position of the board (beside the adding scoring points) - correct?

    With these assumptions, I'm now moving on with defining the board in similar style. Since the board exists only once, there is no need to create class and then an instance of it, like we do for blocks. Therefore, we create a so called singleton instance right away - with state and behavior:

    // brd - Board (or Game) singleton:
    // c: colums
    // r: rows - for testing, start w/ 4 only for square-1 block... ;-)
    // tm: time of interval in milliseconds
    // it: intervall timer that triggers next step in game
    // ls: locations - c x r = x * y = (10x18) array of trues and falses:
    //      occupied = true, not occupied (free) = false
    // sc: scoring (just counting squares, not yet taking time into account)
    // bCs: registered block classes SQ1, BR2,...
    // bs: blocks so far in the game
    // b:  current active block
    // f(xO,yO,ft) - check 'free' for current block b and xO(ffset) and 
    //      yO(ffset) and return true, otherwise false, or, if 3r parm
    //      ft = fatal is true, game over...
    // start() - starts the game / starts a new game
    // nB() - put new block on board (pick nbc(lass) and initial r(otation) randomly)
    // f1() - fall currently active block by +1 along y-axis
    // mB(d) - move current block in d = +/-1 direction along x-axis
    // rB(d) - rotate current block in -/+1 counter/clockwise direction
    // fz() - freeze/settle block in final position (will occupy board locations)
    // gO() - game over - handle interval and invoke draw of score / game over
    // drGO() - draw score and 'game over' to player
    // rBC(c) - register block class
    // init() - initialize... everything (strictly 'speaking: top border
    //      is not really needed, but I keep it for simplicity reasons
    var brd =
    { c: 8, r: 6, tm: 1000
    , it: null
    , ls: []
    , sc: 0
    , bCs: []
    , bs: []
    , b : null
    , f: function(x,y,xO,yO,ps,ft) {
        var i = ps.length, ls = this.ls;
        while (i > 0) { i--;
          if (ls[y + ps[i][1] + yO][x + ps[i][0] + xO]) {
            if (ft) { this.gO(); }
            return false;
        } }
        return true;
      }
    , start: function() {
        if (this.it) { clearInterval(this.it); this.it = null; }
        this.init();
        this.dr();
        this.nB();
        // this.it = setInterval("brd.f1()",this.tm);
        var _this = this;
        this.it = setInterval(function(){ _this.f1(); },this.tm);
      }
    , nB: function() {
        var bc = this.bCs[Math.floor(Math.random() * this.bCs.length)], r , b;
        rx = Math.floor(Math.random() * bc.prototype.rs.length);
        this.bs.push(this.b = (b = new bc(rx)));
        if (this.f(b.x,b.y,0,0,b.ps,false)) {
          this.b.dr();
        } else {
          this.gO();
        } 
      }
    , f1: function() { if (!this.b.f1()) { this.fz(); } }
    , mB: function(d) { this.b.m(d); }
    , rB: function(d) { this.b.r(d); }
    , fz: function() {
        var b = this.b, ps = b.ps, x = b.x, y = b.y, i = ps.length, ls = this.ls;
        while (i > 0) { i--; ls[y + ps[i][1]][x + ps[i][0]] = true; }
        this.sc += b.sc;
        this.nB();
      }
    , gO: function() {
        if (this.it) { clearInterval(this.it); this.it = null; }
        this.drGO();
      }
    , drGO: function() {
        // show scoring and that the game is over...
      }
    , dr: function() {
        this.ls.forEach(function(rls,y){ if ((y > 0) && (y < this.r)) { 
          rls.forEach(function(c,x){ if ((x > 0) && (x < this.c)) { 
            this.drl(x,y,c);
          } },this);
        } },this);
      }
    , drl: function(x,y,c) { setPixel(x,x,c); }
    , rBC: function(bC) { this.bCs.push(bC); }
    , init: function() {
        var rls, y = this.r + 2, x;
        this.ls = [];
        while (y > 0) { y--; 
          this.ls.push(rls = []); x = this.c + 2;
          while (x > 0) { x--;
            rls.push( (x === 0) || (x > this.c) || (y === 0) || ( y > this.r) );
          }
        }
        this.bs = [];
        this.sc = 0;
      }
    };
    

    This code needs for sure more things... for example, when a block is taller than two points the free-check get's out of bound... To make the game more interesting, speed can be varied/increased/interval shortened over time... The 'fall all the way' is missing, and of course all the other blocks...

    For development, I created a simple html page with embedded javascript. Since I do not have the hardware as you have, in the html test I overwrite the drawing methods... to suite the browser environment and canvas drawing. For input, I invoke the board methods from the UI directly. Your input capturing code has to do that in a similar way.

    I attached the html - just click on it and it will run. And as you can see from the screen shot, it also implements the bar with two squares... To look at the code, just view source in the browser.

    Thanks for posting... was a great inspiration --- and of course an example with a much higher success potential than may still unfinished pac man...


    2 Attachments

About

Avatar for allObjects @allObjects started