Arduino Tetris 2

Today, we’re going to continue building on our knowledge. Last time we figured out how to get a single pixel moving around. Since we’ve got that covered, we can move on to drawing shapes and connecting pixels. Let’s get started!

In this series of posts I’m going to teach you how to build your own Arduino Tetris game using a few electronic parts from our Arduino Starter Kits. In yesterday’s post we drew a dot moving on the screen, then controlled it’s location by moving a joystick. Now that we have total pixel control we can draw shapes, and soon after that we can move them around.

What you’ll need:

  • All your knowledge from last tutorial!

Let’s start by drawing a single, full piece. You can do this with any piece of your choice (of course it technically doesn’t have even be from Tetris, but for the sake of it, let’s pick a Tetris piece) – I’m choosing the L shaped piece.

void draw_L1() {
p(0,0,0);            //10
p(0,1,0);            //10
p(0,2,0);            //11
p(1,2,0);
}

I guess we can leave it like this. Coding is somewhat of an art and how you do it isn’t going to match how I do it every time. Consider this – how would you draw the same shape somewhere else on the 8×8 grid? Would you create a new draw_L1 for each piece? What about if the piece rotates? How about all 7 shapes that move and rotate?

Drawing all the pieces

Try and think about it for a second – there’s gotta be a way (there is) to make our life considerably easier. What if there was a way to have one draw() that worked for each and every piece? We could store a picture of each rotation of every piece and then the draw_piece() could be something like this:

draw_piece(piece_x, piece_y, piece_id, piece_rotation)

Pretty neat! What would it look like? Let’s check it out:

                                    // Put all defines at the top of the file, together.  keeps 
                                    // them easy to find.
#define PIECE_W          (4)
#define PIECE_H          (4)
#define NUM_PIECE_TYPES  (7)
                                    // Every piece is PIECE_W wide, PIECE_H tall, and 4 
                                    // rotations.
                                    // 1 means light should be on, 0 means light should be off 
                                    // (or unchanged).
const char piece_I[] = {
  0,0,0,0,
  0,0,0,0,
  1,1,1,1,
  0,0,0,0,

  0,1,0,0,
  0,1,0,0,
  0,1,0,0,
  0,1,0,0,
  
  0,0,0,0,
  0,0,0,0,
  1,1,1,1,
  0,0,0,0,

  0,1,0,0,
  0,1,0,0,
  0,1,0,0,
  0,1,0,0,
};

const char piece_L1[] = {
  0,1,0,0,
  0,1,0,0,
  0,1,1,0,
  0,0,0,0,

  0,0,0,0,
  1,1,1,0,
  1,0,0,0,
  0,0,0,0,
  
  1,1,0,0,
  0,1,0,0,
  0,1,0,0,
  0,0,0,0,

  0,0,1,0,
  1,1,1,0,
  0,0,0,0,
  0,0,0,0,
};

const char piece_L2[] = {
  0,1,0,0,
  0,1,0,0,
  1,1,0,0,
  0,0,0,0,

  1,0,0,0,
  1,1,1,0,
  0,0,0,0,
  0,0,0,0,
  
  0,1,1,0,
  0,1,0,0,
  0,1,0,0,
  0,0,0,0,

  0,0,0,0,
  1,1,1,0,
  0,0,1,0,
  0,0,0,0,
};

const char piece_T[] = {
  0,0,0,0,
  1,1,1,0,
  0,1,0,0,
  0,0,0,0,

  0,1,0,0,
  1,1,0,0,
  0,1,0,0,
  0,0,0,0,

  0,1,0,0,
  1,1,1,0,
  0,0,0,0,
  0,0,0,0,

  0,1,0,0,
  0,1,1,0,
  0,1,0,0,
  0,0,0,0,
};

const char piece_S1[] = {
  1,0,0,0,
  1,1,0,0,
  0,1,0,0,
  0,0,0,0,

  0,1,1,0,
  1,1,0,0,
  0,0,0,0,
  0,0,0,0,

  1,0,0,0,
  1,1,0,0,
  0,1,0,0,
  0,0,0,0,

  0,1,1,0,
  1,1,0,0,
  0,0,0,0,
  0,0,0,0,
};

const char piece_S2[] = {
  0,1,0,0,
  1,1,0,0,
  1,0,0,0,
  0,0,0,0,

  1,1,0,0,
  0,1,1,0,
  0,0,0,0,
  0,0,0,0,
  
  0,1,0,0,
  1,1,0,0,
  1,0,0,0,
  0,0,0,0,

  1,1,0,0,
  0,1,1,0,
  0,0,0,0,
  0,0,0,0,
  0,0,0,0,
};

const char piece_O[] = {
  1,1,0,0,
  1,1,0,0,
  0,0,0,0,
  0,0,0,0,
  
  1,1,0,0,
  1,1,0,0,
  0,0,0,0,
  0,0,0,0,
  
  1,1,0,0,
  1,1,0,0,
  0,0,0,0,
  0,0,0,0,
  
  1,1,0,0,
  1,1,0,0,
  0,0,0,0,
  0,0,0,0,
};

const char *pieces[] = {
  piece_S1,
  piece_S2,
  piece_L1,
  piece_L2,
  piece_O,
  piece_T,
  piece_I,
};

int piece_id=1;                 // try any number from 0 to 6, inclusive.
int piece_rotation=2;           // try any number from 0 to 3, inclusive.
                                // draw a piece from (px,py) to (px+x,py+y) on the grid
void draw_piece() {
  int x, y;
                                // if piece_id is 0 then pieces[piece_id] is equal to piece_I.
  const char *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W);
  
  for(y=0;y<PIECE_H;++y) {
    for(x=0;x<PIECE_W;++x) {
      if( piece[y*PIECE_W+x] == 1 ) {
        p(px+x,py+y,0);
      }
    }
  }
}

Add onre more loop!

void loop() {
              //one_at_a_time();
              //test_p();
              //move_dot();
  draw_piece();
}

Here, we’ve drawn out 7 pieces and each of their four rotations. Each picture is drawn in a 4×4 selection because of the longest piece being a 1×4 – the rotations would mean it would take up a 4×4 area.

Interestingly, we’re making a big assumption here: that 0 <= px+x < GRID_W and 0 <= py+y < GRID_H. If we go outside of this range, strange things would start to happen. Let’s try to avoid that kind of stuff. You don’t want to do the wrong kind of math. It would be … unpleasant.

Joystick Time

Just like before, we’re going to need to combine this with the joystick mechanics.

void move_piece() {
                                // 10 times a second,
  if(millis()-clock>100) {
                                // get ready to wait another 1/10th of a second
    clock=millis();
                                // read the joystick
    int dx=map(analogRead(0),0,1023,500,-500);
    int dy=map(analogRead(1),0,1023,-500,500);
                                // move the piece.  I made a small dead zone in the center of
                                // the joystick so when you let go the dot doesn't drift.
    if(dx> JOYSTICK_DEAD_ZONE) px++;
    if(dx<-JOYSTICK_DEAD_ZONE) px--;
    if(dy> JOYSTICK_DEAD_ZONE) py++;
    if(dy<-JOYSTICK_DEAD_ZONE) py--;
                                // don't let the piece go off the side of the grid
    if(px>GRID_W-PIECE_W) px=GRID_W-PIECE_W;
    if(px<0             ) px=0;
    if(py>GRID_H-PIECE_H) py=GRID_H-PIECE_H;
    if(py<0              ) py=0;
  }
}

Rotating by clicking

Let’s create a new global right after int px. Arduino will use this global to remember the state of the joystick button – basically when it’s being pressed and when it’s realized notated by 1s and 0s, respectively. Take a look at this code segment:

int old_button=0;

Okay, so now we can read from the joystick SW pin (from digital pin 1) to get the state of the joystick button and deal with it.

void rotate_piece() {
                                      // what does the joystick button say
  int new_button = digitalRead(1);
                                      // if the button state has just changed AND it is being 
                                      // let go,
  if( old_button != new_button && new_button > 0 ) {
                                      // figure out what it will look like at that new angle
    int new_pr = ( piece_rotation + 1 ) % 4;
                                      // if it can fit at that new angle (doesn't bump 
                                      // anything) if(can_piece_fit(piece_x,piece_y,new_pr))
    {
                                      // then make the turn.
      piece_rotation = new_pr;
    }
  }
  old_button = new_button;
}

Let’s put it all together in loop().

void loop() {
                         //one_at_a_time();
                         //test_p();
                         //move_dot();
  move_piece();
  rotate_piece();
  draw_piece();
}

Final thoughts

We’ve got pieces. They rotate and move. We can control them, too. Next, we’ll make a piece fall down the screen and stop when it gets to the bottom.

Questions

  • Make the click change the type of piece being drawn. Does it still work if you click more than 7 times?
  • Write your own can_piece_fit(). Use it at the end of move_piece(). Can you get the pieces to all the way to the right? Can you still rotate a piece when it’s all the way to the right?