First touch on Tetris: Multithreading Game based on JFrame and Runnable

1. Description: 

This article focuses on realizing a known and traditional Game named Tetris by using Multithreading mechanism on Java a popular object oriented programming language.

This Game is programmed  by utilizing the 'JFrame', 'JPanel', 'Runnable' and 'KeyListener' interfaces accessed by Java.

Key terminology:

JFrame, JPanel, Runnable, KeyListener

2. Procedures and Codes: 

The basic idea is simple. You should start from the ground and consider all of the possible exceptions and cases. Then, put all of them into codes... Then, debug,debug,debug ... ... Suddenly, you make it !!! 微笑

Well, No kidding anymore...

Let's do the cases pieces by pieces...

Beyond dispute, the role in this game is the little cute square making up of those seven distinct blocks including "L" block , square, reverse "L" block, "T" bolck, swagerly, reverse swagerly, and line piece. 

So, defining the little cute square in class should be the first move.

public class BlockBlock {

    int Block_x;
    int Block_y;
    
    static final int Block_length = 10;
    //create a new object
    public BlockBlock(int x, int y) {
        this.Block_x = x;
        this.Block_y = y;
    }
}

In this class, we define the BlockBlock as the little cute square with location and length.

Since we are designing a graphic game, we still need to paint in thevisible place. So, we need add the following methods for painting the square in 'JPanel' interface.

public void drawBlock(Graphics2D g) {
        Color color = (Tetris.random == 0) ? new Color(78, 238, 148) : (Tetris.random == 1) ? new Color(134, 134, 78)
                : (Tetris.random == 2) ? new Color(205, 115, 115) : (Tetris.random == 3) ? new Color(205, 115, 115)
                                : (Tetris.random == 4) ? new Color(238, 59, 59) : (Tetris.random == 5) ? new Color(238, 59, 59)
                                                : new Color(225, 106, 106);
        Rectangle2D rect = new Rectangle2D.Double(Block_x, Block_y, Block_length, Block_length);
        g.setColor(color);
        g.fill(rect);
        g.setColor(Color.BLACK);
        g.draw(rect);
    }
Besides, in order to make the game more easy, we decide to draw the shadow in the base. Then, the following codes can be added.
//draw the shadow of square in the base
    public void drawBlock_shadow(Graphics2D g) {
        Rectangle2D rect = new Rectangle2D.Double(Block_x, Block_y, Block_length, Block_length);
        g.setColor(new Color(139, 126, 102));
        g.fill(rect);
    }

Last but not least, we have to connect the little cute square to the game, i.e. give some behavior to it. It is clear that in the game we need the block to rotate around its center to be different state to fit different places.

Furthermore, we need each square making up of the blocks to rotate for achieving the above function. In codes, we can define a center for each square and let them rotate around the center 90 degree to achieve the function rotating blocks.

public void Rotate() {
        int temp_x = this.Block_x, temp_y = this.Block_y;
        Block_x = cenX - Block_length / 2 - (temp_y + Block_length / 2 - cenY); // cenX + cenY - temp_y - Block_length
        Block_y = cenY - Block_length / 2 + (temp_x + Block_length / 2 - cenX); // cenY - cenX + temp_x
    }

So, combine all of the codes, we get the completed codes for the BlockBlock Class:

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;

public class BlockBlock {

    int Block_x;
    int Block_y;
    //initialize the center point for the current block including "L" block , square, reverse "L" block, "T" bolck, swagerly, reverse swagerly, and line piece.
    int cenX = Game.Width / 2;
    int cenY = 0;
    
    static final int Block_length = 10;

    //create a new object
    public BlockBlock(int x, int y) {
        this.Block_x = x;
        this.Block_y = y;
    }
    
    //rotate each square around the center point of the current blocks
    public void Rotate() {
        int temp_x = this.Block_x, temp_y = this.Block_y;
        Block_x = cenX - Block_length / 2 - (temp_y + Block_length / 2 - cenY); // cenX + cenY - temp_y - Block_length
        Block_y = cenY - Block_length / 2 + (temp_x + Block_length / 2 - cenX); // cenY - cenX + temp_x
    }
    
    //draw the square in the Plane
    public void drawBlock(Graphics2D g) {
        Color color = (Tetris.random == 0) ? new Color(78, 238, 148) : (Tetris.random == 1) ? new Color(134, 134, 78)
                : (Tetris.random == 2) ? new Color(205, 115, 115) : (Tetris.random == 3) ? new Color(205, 115, 115)
                                : (Tetris.random == 4) ? new Color(238, 59, 59) : (Tetris.random == 5) ? new Color(238, 59, 59)
                                                : new Color(225, 106, 106);
        Rectangle2D rect = new Rectangle2D.Double(Block_x, Block_y, Block_length, Block_length);
        g.setColor(color);
        g.fill(rect);
        g.setColor(Color.BLACK);
        g.draw(rect);
    }
    
    //draw the shadow of square in the base
    public void drawBlock_shadow(Graphics2D g) {
        Rectangle2D rect = new Rectangle2D.Double(Block_x, Block_y, Block_length, Block_length);
        g.setColor(new Color(139, 126, 102));
        g.fill(rect);
    }
}


After creating our hero the little cute square in this game, we are going to consider the whole procedures our hero needs to go through: 

procedure 1: 4 squares appearing as a union for forming distinct blocks.

procedure 2: The block can move left and right and rotate to be different state controlled by players and keeps going down by time. Besides, there is a shadow for the current block in the bottle where obstructors stay.

procedure 3: The block stops as long as it occur any obstructors.

procedure 4: If a horizontal line is filled by square one by one without being interrupted by empty space, the line of squares will be removed.

Those are the all procedures our hero need to go through.

However, in order to make my statements clearly, let's first consider the method we need to add for completing the whole procedures.


First, we need generate a random block for achieving procedure 1.

//give a random type of the control_block.
    public ArrayList Setlist() {
        block_shadown.clear();
        ArrayList<BlockBlock> list = new ArrayList();
        while (true) {
            if ((random = (int) (7 * Math.random())) < 7) {
                break;
            }
        }
        switch (random) {
            case 0:
                //square
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                break;
            case 1:
                //Line 
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length * 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 + BlockBlock.Block_length, 0));
                break;
            case 2:
                //Z_Left 
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 + BlockBlock.Block_length, 0));
                break;
            case 3:
                //Z_Right 
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length * 2, BlockBlock.Block_length));
                break;
            case 4:
                //L_Left
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length * 2));
                break;
            case 5:
                //L_Right 
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length * 2));
                break;
            case 6:
                //iIi.
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2 + BlockBlock.Block_length, 0));
                break;
            default:
                break;
        }
        return list;
    }

In the Setlist() method above we put the blocks containing 4 squares into a list(ArrrayList).


Second, we need to locate the shadow of current block on the bottle for making itvisible.

public void createShadow() {
        block_shadown.clear();
        int longest = Math.abs((Game.Height - 60 - BlockBlock.Block_length) - Lowest().Block_y);
        int incr = 0;
        loop_s01:
        while (incr < longest) {
            incr += BlockBlock.Block_length;
            for (BlockBlock temp_s01 : current_blocks) {
                block_shadown.add(new BlockBlock(temp_s01.Block_x, temp_s01.Block_y + incr));
            }
            loop_s02:
            for (BlockBlock temp_s02 : block_shadown) {
                for (BlockBlock temp_s03 : block_set) {
                    if (temp_s02.Block_x == temp_s03.Block_x && temp_s02.Block_y + BlockBlock.Block_length == temp_s03.Block_y || incr == longest) {
                        break loop_s01;
                    }
                }
            }
            block_shadown.clear();
        }
        if (block_shadown.isEmpty()) {
            int len = (Game.Height - 60 - BlockBlock.Block_length) - Lowest().Block_y;
            for (BlockBlock temp_s01 : current_blocks) {
                block_shadown.add(new BlockBlock(temp_s01.Block_x, len + temp_s01.Block_y));
            }
        }
    }

In the method createShadow() above, block_shadown is an ArrayList was made before for containing the shadow of blocks.

The method used in createShadow() includes Lowest() which is for finding the lowest square of the current block.

//find the control_block's lowest part.
    public BlockBlock Lowest() {
        BlockBlock temp = null;
        for (BlockBlock Temp : current_blocks) {
            temp = (temp == null) ? Temp : temp;
            temp = (temp.Block_y < Temp.Block_y) ? Temp : temp;
        }
        return temp;
    }

The method Lowest() will be used several times in other processings.


Third,  we have to stop the current block as it occurs any obstructors. So, we need a Boolean method help us to judge it.
//judge whether the blocks reach the end.
    public boolean isBottom() {
        for (BlockBlock temp : current_blocks) {
            if (temp.Block_y + BlockBlock.Block_length >= Game.Height - 60) {
                return false;
            }
            for (BlockBlock Temp : block_set) {
                if (temp.Block_x == Temp.Block_x && temp.Block_y + BlockBlock.Block_length >= Temp.Block_y) {
                    return false;
                }
            }
        }
        return true;
    }


Fourth, as the block reach its bottom, we need to start procedure 4 which is the core thing and every player aims at.

//remove the blocks in the sameline(when the nummber reach a certain number).
    public void remove_block() {
        block_temp.clear();
        BlockBlock temp = null;
        for (BlockBlock Temp : block_set) {
            temp = (temp == null) ? Temp : temp;
            if (temp.Block_y != Temp.Block_y) {
                block_temp.clear();
                temp = Temp;
            }
            if (temp.Block_y == Temp.Block_y) {
                block_temp.add(Temp);
                if (block_temp.size() == 22) {
                    block_set.removeAll(block_temp);
                    for (BlockBlock TEMP : block_set) {
                        TEMP.Block_y += (TEMP.Block_y < block_temp.get(0).Block_y) ? BlockBlock.Block_length : 0;
                    }
                    break;
                }
            }
        }
    }


Fifth, this is a graphic game, the most important thing is to show the dynamic images to the player. In this place, we adopt the double buffed drawing method in the JPanel interface.

@Override
    public void update(Graphics g) {
        paint(g);
    }

    @Override
    public void paint(Graphics g) {
        Gub = (Gub == null) ? this.createImage(this.getWidth(), this.getHeight()) : Gub;
        g_p = (g_p == null) ? Gub.getGraphics() : g_p;
        g2D = (g2D == null) ? (Graphics2D) g_p : g2D;
        g2D.setColor(this.getBackground());
        g2D.fillRect(0, 0, getWidth(), getHeight());
        //draw the table of block.
        g2D.setColor(Color.GRAY);
        for (int i = 0; i < 23; i++) {
            g2D.drawLine(i * BlockBlock.Block_length, 0, i * BlockBlock.Block_length, Game.Height - 60);
        }
        for (int n = 0; n < 61; n++) {
            g2D.drawLine(0, n * BlockBlock.Block_length, 220, n * BlockBlock.Block_length);
        }
        current_blocks.stream().forEach((temp01) -> {
            temp01.drawBlock(g2D);
        });
        try {
            block_set.stream().forEach((temp02) -> {
                temp02.drawBlock(g2D);
            });
        } catch (Exception e_set) {
        }
        try {
            block_shadown.stream().forEach((Temp) -> {
                Temp.drawBlock_shadow(g2D);
            });
        } catch (Exception e_shadown) {
        }
        g.drawImage(Gub, 0, 0, this);
    }


Last, we have to consider the interactive method for allowing players control the current block.

By using the interface: KeyListener, we can make our codes react to players through keyboard.

//some keyListener for control.
    @Override
    public void keyPressed(KeyEvent e01) {
        switch (e01.getKeyCode()) {
            case KeyEvent.VK_LEFT:
                block_shadown.clear();
                if (isInside_Left()) {
                    current_blocks.stream().map((temp) -> {
                        temp.Block_x -= BlockBlock.Block_length;
                        return temp;
                    }).forEach((temp) -> {
                        temp.cenX -= BlockBlock.Block_length;
                    });
                }
                break;
            case KeyEvent.VK_RIGHT:
                block_shadown.clear();
                if (isInside_Right()) {
                    current_blocks.stream().map((Temp) -> {
                        Temp.Block_x += BlockBlock.Block_length;
                        return Temp;
                    }).forEach((Temp) -> {
                        Temp.cenX += BlockBlock.Block_length;
                    });
                }
                break;
            case KeyEvent.VK_SPACE:
                block_shadown.clear();
                current_blocks.stream().forEach((temP) -> {
                    temP.Rotate();
                });
                break;
            case KeyEvent.VK_ENTER:
                done();
                break;
            default:
                break;
        }
    }
    @Override
	public void keyReleased(KeyEvent arg0) {
		// TODO Auto-generated method stub
	}


	@Override
	public void keyTyped(KeyEvent arg0) {
		// TODO Auto-generated method stub		
	}
    //when player press "Enter" make the block reach the bottom.
    public void done() {
        if (!block_shadown.isEmpty()) {
            current_blocks.clear();
            current_blocks.addAll(block_shadown);
            for (BlockBlock temp : current_blocks) {
                temp.Block_y -= BlockBlock.Block_length;
            }
        } else {
            int len = (Game.Height - 60 - BlockBlock.Block_length) - Lowest().Block_y;
            for (BlockBlock temp : current_blocks) {
                temp.Block_y += len - BlockBlock.Block_length;
            }
            System.out.print(current_blocks);
        }
    }
    
    //control the block inside the window.
    public boolean isInside_Left() {
        return current_blocks.stream().noneMatch((temp) -> (temp.Block_x <= 0));
    }


    public boolean isInside_Right() {
        return current_blocks.stream().noneMatch((temp) -> (temp.Block_x + BlockBlock.Block_length >= 220));
    }

In this method, 'space' is designed for rotating the block, 'enter' is designed for making the block reach its bottom quickly, and the direction keys: '<-' and '->' are designed for controlling the block moving left and right.


Then, we need to consider the arrangement for those methods and the procedures' scheming over time.

Here, we use the interface: Runnable to achieve that.

//start the game.
    @Override
    public void run() {
        Loop01:
        while (true) {
            for (BlockBlock temp : block_set) {
                if (temp.Block_y <= BlockBlock.Block_length) {
                    break Loop01;
                }
            }
            //make the block move speed per pause_time.
            current_blocks.stream().map((temp) -> {
                temp.Block_y += speed;
                return temp;
            }).forEach((temp) -> {
                temp.cenY += speed;
            });
            //judge whether the block reach the end.
            if (!isBottom()) {
                current_blocks.stream().forEach((temp) -> {
                    block_set.add(temp);
                });
                current_blocks = Setlist();
            }
            //remove the blocks
            remove_block();
            createShadow();
            //thread sleep_pause time.
            try {
                Thread.sleep(90);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //paint all of the item.
            repaint();
        }
    }


 After combining all of the codes, we can have a complete class for starting the Tetris game:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.TreeSet;
import javax.swing.JPanel;

public class Tetris extends JPanel implements Runnable, KeyListener {

    static final int speed = 10;
    static int random;
    static ArrayList<BlockBlock> block_temp = new ArrayList();
    ArrayList<BlockBlock> block_shadown = new ArrayList();
    //here re_write compare() method for make set have a way to sort the object inside.
    TreeSet<BlockBlock> block_set = new TreeSet(new Comparator() {
        @Override
        public int compare(Object p1, Object p2) {
            BlockBlock b01 = (BlockBlock) p1;
            BlockBlock b02 = (BlockBlock) p2;
            if (b01.Block_y > b02.Block_y) {
                return 1;
            }
            if (b01.Block_y == b02.Block_y) {
                return (b01.Block_x > b02.Block_x) ? 1 : (b01.Block_x < b02.Block_x) ? -1 : 0;
            }
            return -1;
        }
    });
    ArrayList<BlockBlock> current_blocks = Setlist();
    //buffer_draw
    Image Gub;
    Graphics2D g2D;
    Graphics g_p;

    public Tetris() {
        Thread block_game = new Thread(this);
        this.setBounds(0, 0, Game.Width, Game.Height);
        this.setFocusable(true);
        this.addKeyListener(this);
        block_game.start();
    }

    //start the game.
    @Override
    public void run() {
        Loop01:
        while (true) {
            for (BlockBlock temp : block_set) {
                if (temp.Block_y <= BlockBlock.Block_length) {
                    break Loop01;
                }
            }
            //make the block move speed per pause_time.
            current_blocks.stream().map((temp) -> {
                temp.Block_y += speed;
                return temp;
            }).forEach((temp) -> {
                temp.cenY += speed;
            });
            //judge whether the block reach the end.
            if (!isBottom()) {
                current_blocks.stream().forEach((temp) -> {
                    block_set.add(temp);
                });
                current_blocks = Setlist();
            }
            //remove the blocks
            remove_block();
            createShadow();
            //thread sleep_pause time.
            try {
                Thread.sleep(90);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //paint all of the item.
            repaint();
        }
    }

    //give a random type of the control_block.
    public ArrayList Setlist() {
        block_shadown.clear();
        ArrayList<BlockBlock> list = new ArrayList();
        while (true) {
            if ((random = (int) (7 * Math.random())) < 7) {
                break;
            }
        }
        switch (random) {
            case 0:
                //square
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                break;
            case 1:
                //Line 
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length * 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 + BlockBlock.Block_length, 0));
                break;
            case 2:
                //Z_Left 
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 + BlockBlock.Block_length, 0));
                break;
            case 3:
                //Z_Right 
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length * 2, BlockBlock.Block_length));
                break;
            case 4:
                //L_Left
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length * 2));
                break;
            case 5:
                //L_Right 
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, BlockBlock.Block_length * 2));
                break;
            case 6:
                //iIi.
                list.add(new BlockBlock(Game.Width / 2, 0));
                list.add(new BlockBlock(Game.Width / 2, BlockBlock.Block_length));
                list.add(new BlockBlock(Game.Width / 2 - BlockBlock.Block_length, 0));
                list.add(new BlockBlock(Game.Width / 2 + BlockBlock.Block_length, 0));
                break;
            default:
                break;
        }
        return list;
    }
    
    public void createShadow() {
        block_shadown.clear();
        int longest = Math.abs((Game.Height - 60 - BlockBlock.Block_length) - Lowest().Block_y);
        int incr = 0;
        loop_s01:
        while (incr < longest) {
            incr += BlockBlock.Block_length;
            for (BlockBlock temp_s01 : current_blocks) {
                block_shadown.add(new BlockBlock(temp_s01.Block_x, temp_s01.Block_y + incr));
            }
            loop_s02:
            for (BlockBlock temp_s02 : block_shadown) {
                for (BlockBlock temp_s03 : block_set) {
                    if (temp_s02.Block_x == temp_s03.Block_x && temp_s02.Block_y + BlockBlock.Block_length == temp_s03.Block_y || incr == longest) {
                        break loop_s01;
                    }
                }
            }
            block_shadown.clear();
        }
        if (block_shadown.isEmpty()) {
            int len = (Game.Height - 60 - BlockBlock.Block_length) - Lowest().Block_y;
            for (BlockBlock temp_s01 : current_blocks) {
                block_shadown.add(new BlockBlock(temp_s01.Block_x, len + temp_s01.Block_y));
            }
        }
    }

    //remove the blocks in the sameline(when the nummber reach a certain number).
    public void remove_block() {
        block_temp.clear();
        BlockBlock temp = null;
        for (BlockBlock Temp : block_set) {
            temp = (temp == null) ? Temp : temp;
            if (temp.Block_y != Temp.Block_y) {
                block_temp.clear();
                temp = Temp;
            }
            if (temp.Block_y == Temp.Block_y) {
                block_temp.add(Temp);
                if (block_temp.size() == 22) {
                    block_set.removeAll(block_temp);
                    for (BlockBlock TEMP : block_set) {
                        TEMP.Block_y += (TEMP.Block_y < block_temp.get(0).Block_y) ? BlockBlock.Block_length : 0;
                    }
                    break;
                }
            }
        }
    }

    //find the control_block's lowest part.
    public BlockBlock Lowest() {
        BlockBlock temp = null;
        for (BlockBlock Temp : current_blocks) {
            temp = (temp == null) ? Temp : temp;
            temp = (temp.Block_y < Temp.Block_y) ? Temp : temp;
        }
        return temp;
    }
    
    //judge whether the blocks reach the end.
    public boolean isBottom() {
        for (BlockBlock temp : current_blocks) {
            if (temp.Block_y + BlockBlock.Block_length >= Game.Height - 60) {
                return false;
            }
            for (BlockBlock Temp : block_set) {
                if (temp.Block_x == Temp.Block_x && temp.Block_y + BlockBlock.Block_length >= Temp.Block_y) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public void update(Graphics g) {
        paint(g);
    }

    @Override
    public void paint(Graphics g) {
        Gub = (Gub == null) ? this.createImage(this.getWidth(), this.getHeight()) : Gub;
        g_p = (g_p == null) ? Gub.getGraphics() : g_p;
        g2D = (g2D == null) ? (Graphics2D) g_p : g2D;
        g2D.setColor(this.getBackground());
        g2D.fillRect(0, 0, getWidth(), getHeight());
        //draw the table of block.
        g2D.setColor(Color.GRAY);
        for (int i = 0; i < 23; i++) {
            g2D.drawLine(i * BlockBlock.Block_length, 0, i * BlockBlock.Block_length, Game.Height - 60);
        }
        for (int n = 0; n < 61; n++) {
            g2D.drawLine(0, n * BlockBlock.Block_length, 220, n * BlockBlock.Block_length);
        }
        current_blocks.stream().forEach((temp01) -> {
            temp01.drawBlock(g2D);
        });
        try {
            block_set.stream().forEach((temp02) -> {
                temp02.drawBlock(g2D);
            });
        } catch (Exception e_set) {
        }
        try {
            block_shadown.stream().forEach((Temp) -> {
                Temp.drawBlock_shadow(g2D);
            });
        } catch (Exception e_shadown) {
        }
        g.drawImage(Gub, 0, 0, this);
    }

    //some keyListener for control.
    @Override
    public void keyPressed(KeyEvent e01) {
        switch (e01.getKeyCode()) {
            case KeyEvent.VK_LEFT:
                block_shadown.clear();
                if (isInside_Left()) {
                    current_blocks.stream().map((temp) -> {
                        temp.Block_x -= BlockBlock.Block_length;
                        return temp;
                    }).forEach((temp) -> {
                        temp.cenX -= BlockBlock.Block_length;
                    });
                }
                break;
            case KeyEvent.VK_RIGHT:
                block_shadown.clear();
                if (isInside_Right()) {
                    current_blocks.stream().map((Temp) -> {
                        Temp.Block_x += BlockBlock.Block_length;
                        return Temp;
                    }).forEach((Temp) -> {
                        Temp.cenX += BlockBlock.Block_length;
                    });
                }
                break;
            case KeyEvent.VK_SPACE:
                block_shadown.clear();
                current_blocks.stream().forEach((temP) -> {
                    temP.Rotate();
                });
                break;
            case KeyEvent.VK_ENTER:
                done();
                break;
            default:
                break;
        }
    }
    @Override
	public void keyReleased(KeyEvent arg0) {
		// TODO Auto-generated method stub
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
		// TODO Auto-generated method stub		
	}
    //when player press "Enter" make the block reach the bottom.
    public void done() {
        if (!block_shadown.isEmpty()) {
            current_blocks.clear();
            current_blocks.addAll(block_shadown);
            for (BlockBlock temp : current_blocks) {
                temp.Block_y -= BlockBlock.Block_length;
            }
        } else {
            int len = (Game.Height - 60 - BlockBlock.Block_length) - Lowest().Block_y;
            for (BlockBlock temp : current_blocks) {
                temp.Block_y += len - BlockBlock.Block_length;
            }
            System.out.print(current_blocks);
        }
    }
    
    //control the block inside the window.
    public boolean isInside_Left() {
        return current_blocks.stream().noneMatch((temp) -> (temp.Block_x <= 0));
    }

    public boolean isInside_Right() {
        return current_blocks.stream().noneMatch((temp) -> (temp.Block_x + BlockBlock.Block_length >= 220));
    }
}

Finally, what we need is to build a frame for running this game. 

In this place, the interface: JFrame is adopted for building a window running this game.

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

public class Game extends JFrame {

    static int Width = 300;
    static int Height = 660;

    public Game() {
        Container RUN_GAME = new Tetris();
        this.setContentPane(RUN_GAME);
        this.setBounds(500, 20, Width, Height);
        this.setTitle("My Tetris");
        this.setVisible(true);
        this.setResizable(false);
    }

    public static void main(String[] args) {
        Game game = new Game();
        game.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    }
}

3. Outcome: 


(Applied Math is dreaming of coding ... ...)

Author: Clarence

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值