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

Java实现俄罗斯方块
本文介绍了一种使用Java多线程机制实现经典游戏俄罗斯方块的方法。通过利用JFrame、JPanel、Runnable和KeyListener等接口,实现了游戏的图形界面及交互功能。

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

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究”展开,提出了一种结合数据驱动方法与Koopman算子理论的递归神经网络(RNN)模型线性化方法,旨在提升纳米定位系统的预测控制精度与动态响应能力。研究通过构建数据驱动的线性化模型,克服了传统非线性系统建模复杂、计算开销大的问题,并在Matlab平台上实现了完整的算法仿真与验证,展示了该方法在高精度定位控制中的有效性与实用性。; 适合人群:具备一定自动化、控制理论或机器学习背景的科研人员与工程技术人员,尤其是从事精密定位、智能控制、非线性系统建模与预测控制相关领域的研究生与研究人员。; 使用场景及目标:①应用于纳米级精密定位系统(如原子力显微镜、半导体制造设备)中的高性能预测控制;②为复杂非线性系统的数据驱动建模与线性化提供新思路;③结合深度学习与经典控制理论,推动智能控制算法的实际落地。; 阅读建议:建议读者结合Matlab代码实现部分,深入理解Koopman算子与RNN结合的建模范式,重点关注数据预处理、模型训练与控制系统集成等关键环节,并可通过替换实际系统数据进行迁移验证,以掌握该方法的核心思想与工程应用技巧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值