[3] Color Matrix

Introduction

color_matrix_game This is a game of falling block. The purpose of the game is to match three blocks of the same color in a row.

Game Control

  • LEFT ARROW : Move left
  • RIGHT ARROW : Move right
  • DOWN ARROW : Fast Falling
  • SPACE : Rotate
  • C : Capture Screen and save it to screen.png
The image at the beginning of this post was captured by pressing ‘C’ during game play. I implemented the screen capture because I am coding in Lubuntu Linux, which doesn’t support the “Print Screen” button in the default installation. The screen capture coding is very simple and a stand alone demonstration can be found here

KeyListener

It is very similar to the one in the game of Space Invader. We just set a global variable when the key is pressed, and reset the variable when the key is released. The status global variables is used in the control() method inside ColorMatrix.java

Sprite Management

As usual, we will use an ArrayList to hold the sprite. The basic unit of the game is a block. This is defined in BlockSprite.java. Unlike the game of Space Invader, there is only one kind of Sprite in this game. And the sprite doesn’t have self animation. To see an example of a self animated sprite, see EnemySprite.java in the game of Space Invader.

Remark: Even the BlockSprite object doesn’t have self animation, we would still call its animate() method in each frame. The animate() method in BlockSprite.java is very simple.
  public void animate()
  {
    animationFrame++;    

    if (direction==LEFT)
    {
      x-=moveStep;
    }
    else if (direction==RIGHT)
    {
      x+=moveStep;
    }

  }
 
The direction variable would be updated inside the control() method in ColorMatrix.java, which is actually initiated by player key pressed.

Graphics

MATCH 3 by Buch/CC0
We just need a single file (match3_sheet_0.png) provided by Buch:
match3_sheet_0

Types of Blocks

We don’t need so many types of blocks, we just select seven types and their bounding rectangle inside the PNG file is defined in BlockSprite.java
  static int[][] imageBounds = new int[][] {  // [blockType][x,y,w,h]
    null,                // reserved for an empty image
    {209,202, 32, 32},   // Grey block with nothing inside
    {  4,268, 32, 32},   // Purple block with nothing inside 
    {242, 70, 32, 32},   // Red block with a cross inside
    { 70,136, 32, 32},   // Green block with a circle inside
    {308,136, 32, 32},   // Brown block with a square inside
    {341,169, 32, 32},   // Orance block with a triangle inside 
    {169,169, 32, 32}    // Blue block with a star inside
  };


Static Variables

Inside BlockSprite.java, we use a few static variables. This is because they are the same among all blocks. For example, all blocks move 4 pixels at a time, all blocks are chosen from the same table of bounding rectangles, all blocks are moving at every 64 frames in level 1.
class BlockSprite extends Sprite
{

  static int[][] imageBounds = new int[][] (see above...)  
  static int moveWait=64;  // fall every 64 frames (in level 1) 
  static int moveStep=4;  
}
On the contrary, each block has different color and status, hence they are not declared static.
class BlockSprite extends Sprite
{
  int blockType;  // PURPLE, RED, GREEN, ... , see BlockType.java
  boolean flashing=false;
  boolean dropping=false; 
}
Compile and play the game and you will understand what is the meaning of a flashing or dropping block.

Game Loop

In the game of Space Invader, we use a Swing Timer to control the game flow.
    // call animateFrame() for every 30ms
    javax.swing.Timer timer=new javax.swing.Timer(30, (e)->animateFrame());
    timer.start();


Since this is a programming blog, I wish to show something new for each post. Hence I will use an alternative approach as follows:
  void gameLoop()
  {
    while (true)
    {
      animateFrame();
      refresh();
      wait(15);  // wait for 15 ms
    }
  }

Instead of using a Swing Timer, we write a loop explicitly. This approach has three advantages over the Swing Timer.
  1. On some platforms, Swing Timer resolution cannot be lowered than 30ms
  2. It is simpler to pause the game flow and display some special effects before resuming the normal game flow. See flash() and drop() for details.
  3. More accurate frame rate can be achieved by proper implementation of wait(). That’s why we use wait(15) instead of sleep(15). See ColorMatrix.java to see the implementation of the wait() function.


Board and Cell Dimension

We define the board and cell dimensions in Board.java and Cell.java just like the game of Klotski
class Board
{
  // define a board with WxH cells
  public static final int W=6; 
  public static final int H=12;

}

class Cell
{
  static final int W=32;
  static final int H=32;
}
A cell is just a place holder for a block and its dimension is 32 by 32 pixels. The game board contains 6×12 cells

Movement and Alignment

Since the block dimension is 32×32 pixels, if we always move the block with a step of 32 pixels, then we will always align to the boundary of a cell. However, a move step of 32 pixels is just too big for a smooth animation. The movement would become very choppy if we choose such a big move step.

Therefore we choose a much smaller move step of 4 pixels, which is defined in BlockSprite.java
  static int moveStep=4;  
This, however, creates another problem. We want to ensure whenever a block reaches the floor and stops moving, it must be align to the cell boundary. That is, we don’t want the following unaligned block. unaligned To prevent unaligned block, we implement the align() function in ColorMatrix.java. And that is the first function call when the falling block is fallen to the floor.
  // the falling token has become the fallen blocks
  void fallen(int dx)
  {
    // make sure the block is horizontally align to cell boundry
    align(dx);
    // other codes omitted ...
  }
The align() function is very straight forward. It just moves the block to the next cell boundary according to the current moving direction.

Matching

When a falling block reaches the floor or the top of some other blocks, we start the matching process. For every block, We just scan four directions and count the number of block with the same color. If three or more same-color blocks are found, we mark all matched blocks as flashing. The four directions are east, south, southeast and northeast. We don’t need to scan all the eight directions because if we can match 3 blocks scanning towards the west, then there must be a match by scanning from the other block towards the east. For example : match

Scoring

The scoring formula is simple, the basic score for a match is 100, and it will be increased by 5% for every additional block. The scoring formula is defined in the function fallen().
    if (totalClear!=0)
    {
      double clearScore = 100;
      for (int i=0;i<totalClear;i++)
        clearScore *= 1.05;
      score += clearScore;
      scoreLabel.setText("SCORE : "+score);
    }    


Level

The level will be increased by 1 for every 5 fallen blocks. In level 1, the block will move down 4 pixels for every 64 frames. To adjust difficulty, we will decrease this number while level increase, hence the block will be falling faster as level increase. The logic is also implemented inside the function fallen() in ColorMatrix.java
    if (level!=oldLevel) 
    {
      int[] wait=new int[] {64,48,32,24,16,8,4,3,2,1 };
      BlockSprite.moveWait = wait[(level-1) % wait.length];
    }


Token

A token is just a collection of 3 blocks. The Token object is defined in Token.java. The token is the only object that is controlled by the player. It implements the rotate() and the setDirection() function.

Source Code

To compile and run the program, visit the source code page and follow the instructions.

Previous Entries [2] Space Invader Next Entries [4] Dot Matrix