A Game is a World with a Timer. 

Game sends KeyEvents to the World. 

The World contains:

  Shape objects (several kinds as a matter of fact)

  Background object 

Both Shape objects and the Background contain Squares. 

The Square object is the most fundamental of all. 

The Background is an ArrayList<ArrayList<Square>>.

Each Shape should have/build a Map<String, Square>. 

The map indicates the coordinate where the square is. 

My game will send key events and the timer action events. 

The basic loop does not exist per se it's in the timer. 

So actionPerformed is like this:

  Shape current; is defined in the world as an instance variable. 

The code for action performed: 

  if (! current.moveDown()) {
    background.receive(current);
    current = Shape.randomShape(...);    
  } else {
    
  } 
  this.repaint(); // update the view (user) 

So with this I build the program in stages:

import javax.swing.*; 

class Game extends JFrame {
  Game() {
    World world = new World(); 
    this.add(world); 
    world.start(); 
    this.addKeyListener(world); 
    this.setSize(200, 400);
    this.setVisible(true); 
    this.setDefaultCloseOperation(EXIT_ON_CLOSE); 
  }
  public static void main(String[] args) {
    Game game = new Game();  
  }
}

//-------------------------------------------------

import java.awt.*; 

class Square {
  World world; 
  Color color;
  int row, column; // distance to top left corner of World 
  Square(World world, Color color, int row, int col) {
    this.world = world; 
    this.color = color; 
    this.row = row; 
    this.column = col; 
  }
  void draw(Graphics g) {
    int width = world.squareWidth(), height = world.squareHeight(); 
    int x = this.column * width, y = this.row * height; 
    g.setColor(this.color); 
    g.fillRect(x, y, width, height); 
    g.setColor(Color.WHITE); 
    g.drawRect(x, y, width, height); 
    g.setColor(Color.BLACK); 
  }
  public String toString() {
    return "Square(" + row + ", " + column + ")";  
  }
  boolean conflictsWith(int rows, int cols) {
    return row < 0 || row >= rows || column < 0 || column >= cols;  
  }
}

//-------------------------------------------------

import java.awt.*; 
import java.util.*; 

abstract class Shape {
  
  void draw(Graphics g) {
    for (String key : squares.keySet()) {
      Square square = squares.get(key); 
      if (square != null) 
        square.draw(g); 
    } 
  }

  Map<String, Square> squares;   
  
  boolean moveLeft() { 
    Map<String, Square> future = this.createMap(row, column-1); 
    if (true) { // world.validates(future)) {
      this.squares = future; 
      this.column -= 1; 
      return true; 
    } else {
      return false;  
    }
  } 

  boolean moveRight() { 
    Map<String, Square> future = this.createMap(row, column+1); 
    if (true) { // world.validates(future)) {
      this.squares = future; 
      this.column += 1; 
      return true; 
    } else {
      return false;  
    }
  } 
  
  Map<String, Square> createMap(int row, int column) {
    Map<String, Square> squares = new HashMap<String, Square>(); 
    int[][] matrix = this.rotations[this.rotation];
    for (int i = 0; i < matrix.length; i++) {
      for (int j = 0; j < matrix[i].length; j++) {
        Square square = matrix[i][j] == 1 ? new Square(world, color, row + i, column + j) : null; 
        squares.put(i + ", " + j, square); 
      }
    }
    return squares; 
  }
  
  World world; 
  int row, column;
    
  Color color;
  
  Shape(int row, int column, World world) {
    this.row = row; 
    this.column = column; 
    this.world = world;
    this.squares = new HashMap<String, Square>(); 
  }
  
  int rotation; 
  
  int[][][] rotations; 
  
  static Shape randomShape(World world) {
    int which = (int)(Math.random() * 7 + 1); 
    switch (which) {
      // case 1 : return new SquareShape   (0, world.COLS/2, world);  
      // case 2 : return new LShape        (0, world.COLS/2, world);
      // case 3 : return new SShape        (0, world.COLS/2, world); 
      // case 4 : return new ZShape        (0, world.COLS/2, world);
      // case 5 : return new MirroredLShape(0, world.COLS/2, world);
      // case 6 : return new LineShape     (0, world.COLS/2, world);
      default: return new TShape        (0, world.COLS/2, world);
    }
  }
  
}

//-------------------------------------------------


import java.awt.*; 

class TShape extends Shape {
  TShape(int row, int column, World world) {
    super(row, column, world); 
    this.color = new Color(204, 204, 102);
    this.rotations = new int[][][] {
      { { 1, 1, 1}, 
        { 0, 1, 0}, 
        { 0, 0, 0}}, //-------------------
      { { 0, 0, 1}, 
        { 0, 1, 1}, 
        { 0, 0, 1}}, //-------------------
      { { 0, 0, 0}, 
        { 0, 1, 0}, 
        { 1, 1, 1}}, //-------------------
      { { 1, 0, 0}, 
        { 1, 1, 0}, 
        { 1, 0, 0}}  //------------------- 
    };
    this.squares = createMap(row, column); 
  }
  public String toString() {
    return this.squares + ": " + super.toString();  
  }

}

//-------------------------------------------------

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*;

class World extends JComponent implements KeyListener, ActionListener {
  public void keyPressed(KeyEvent e) {
    int keycode = e.getKeyCode();
    System.out.println("Ouch. You have typed: " + ((char) keycode)); 
    switch (keycode) {
      case KeyEvent.VK_LEFT: 
        System.out.println("Left arrow key.");    
        this.current.moveLeft(); 
        this.repaint(); 
        break; 
      case KeyEvent.VK_RIGHT: 
        System.out.println("Right arrow key."); 
        this.current.moveRight(); 
        this.repaint();       
      break; 
      case KeyEvent.VK_UP   : System.out.println("Up arrow key."); break; 
      case KeyEvent.VK_DOWN : System.out.println("Down arrow key."); break; 
      default      : System.out.println("No arrow key."); break; 
    }
  }
  public void keyReleased(KeyEvent e) { } 
  public void keyTyped(KeyEvent e) { } 
  int count;
  Timer timer; 
  public void actionPerformed(ActionEvent e) {
    this.count += 1; 
    System.out.println( "ActionEvent received: " + this.count );  
  }
  World() {
    this.timer = new Timer(1000, this); 
    this.current = Shape.randomShape(this); 
  }
  void start() {
    this.timer.start();  
  }
  Shape current; 
  public void paintComponent(Graphics g) {
    this.current.draw(g); 
  }
  
  static final int ROWS = 22;
  static final int COLS = 10; 
  int squareWidth() { return (int) getSize().getWidth() / World.COLS; }
  int squareHeight() { return (int) getSize().getHeight() / World.ROWS; }
}

//-------------------------------------------------

So that's stage two. 

Next I should worry about the background.