Java Tetris rotation bug

244 views Asked by At

I'm having some problems with Tetris. So first of all, I have a class Shape, and then subclasses of Shape for each shape type. This is how a Shape subclass looks like:

public class SquareShape extends Shape {
    public SquareShape(){
        coords = squareShapeCoords;
        shape = SQUARESHAPE;
    }
}

In the Shape class I have a rotate method as follows:

protected void setX(int i, int x) { coords[i][0] = x; }
protected void setY(int i, int y) { coords[i][1] = y; }
public int x(int i) { return coords[i][0]; }
public int y(int i) { return coords[i][1]; }

public Shape rotate(){
        if (this.getShape().equals(SQUARESHAPE)) return this;
        Shape newShape = new Shape();
        newShape.shape = this.shape;
        for (int i = 0; i < 4; i++) {
            newShape.setX(i, y(i));
            newShape.setY(i, -x(i));
        }
        return newShape;
}

Note that I store the coordinates of each shape in 2D arrays. Also, this is my game engine class:

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

public class GameEngine extends JPanel implements ActionListener{

    private final int HEIGHT = 15;
    private final int WIDTH = 10;
    private int score;
    int coordX = 0;
    int coordY = 0;
    Timer timer;
    boolean isFinishedFalling = false;
    boolean isRunning = false;
    boolean isPaused = false;
    Shape block;
    String[] shapes;


    public GameEngine(){
        setFocusable(true);
        block = new Shape();
        timer = new Timer(600, this);
        timer.start();
        addMouseListener(new MAdapter());
        addMouseWheelListener(new WheelAdapter());
        addKeyListener(new KAdapter());
        setBackground(Color.BLACK);
        shapes = new String[WIDTH * HEIGHT];
        clearShapes();
    }

    int squareWidth() { return (int) getSize().getWidth() / WIDTH; }
    int squareHeight() { return (int) getSize().getHeight() / HEIGHT; }
    String shapeAt(int x, int y) { return shapes[(y * WIDTH) + x]; }
    public int getScore() {return score;}

    public void actionPerformed(ActionEvent e){
        if(isFinishedFalling){
            isFinishedFalling = false;
            newBlock();
        } else moveDown();
    }

    private boolean move(Shape newShape, int newCoordX, int newCoordY)
    {
        for (int i = 0; i < 4; ++i) {
            int x = newCoordX + newShape.x(i);
            int y = newCoordY - newShape.y(i);
            if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return false;
            if (!shapeAt(x, y).equals(Shape.DEFAULTSHAPE)) return false;
        }

        block = newShape;
        coordX = newCoordX;
        coordY = newCoordY;
        repaint();
        return true;
    }

    private boolean moveLeft() { return move(block, coordX-1, coordY);}
    private boolean moveRight() { return move(block, coordX+1, coordY);}
    private boolean moveDown(){
        if(!move(block, coordX, coordY-1)){
            blockIsDown();
            return false;
        } else return true;
    }

    private void dropDown(){
        int y = coordY;
        while(y>0){
            if(!move(block, coordX, y-1)) break;
            y -= 1;
        }
        blockIsDown();
    }

    private boolean rotate() { return move(block.rotate(), coordX, coordY);}

    private void blockIsDown(){
        for(int i=0; i<4; i++){
            int a = coordX + block.x(i);
            int b = coordY - block.y(i);
            shapes[b * WIDTH + a] = block.getShape();
        }
        clearFullLines();
        if(!isFinishedFalling) newBlock();
    }

    private void clearFullLines(){
        int fullLines = 0;
        for(int i = HEIGHT-1; i>=0; i--){
            boolean lineFull = true;
            for(int j=0; j<WIDTH; j++){
                if(shapeAt(j, i).equals(Shape.DEFAULTSHAPE)){
                    lineFull = false;
                    break;
                }
            }
            if(lineFull){
                fullLines++;
                for(int m=i; m<HEIGHT-1; m++){
                    for(int n=0; n<WIDTH; n++)
                        shapes[(m*WIDTH) + n] = shapeAt(n, m+1);
                }
            }
        }
        if(fullLines>0){
            score += fullLines*100;
            isFinishedFalling = true;
            block.setShape(Shape.DEFAULTSHAPE);
            repaint();
        }

    }

    private void newBlock()
    {
        block = new RandomShape();
        coordX = WIDTH / 2 + 1;
        coordY = HEIGHT - 1 + block.minY();

        if (!move(block, coordX, coordY)) {
            block.setShape(Shape.DEFAULTSHAPE);
            timer.stop();
            isRunning = false;
        }
    }

    private void clearShapes(){
        for(int i=0; i< WIDTH * HEIGHT; i++) shapes[i] = Shape.DEFAULTSHAPE;
    }

    private void drawSquare(Graphics g, int x, int y, String shape){
        Color color = Color.BLACK;
        if(shape.equals(Shape.ZSHAPE)) color = Color.GREEN;
        if(shape.equals(Shape.SSHAPE)) color = Color.RED;
        if(shape.equals(Shape.LINESHAPE)) color = Color.CYAN;
        if(shape.equals(Shape.TSHAPE)) color = Color.BLUE;
        if(shape.equals(Shape.SQUARESHAPE)) color = Color.YELLOW;
        if(shape.equals(Shape.LSHAPE)) color = Color.MAGENTA;
        if(shape.equals(Shape.MIRROREDLSHAPE)) color = Color.ORANGE;

        g.setColor(color);
        g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);
        g.setColor(color.brighter());
        g.drawLine(x, y + squareHeight() - 1, x, y);
        g.drawLine(x, y, x + squareWidth() - 1, y);

        g.setColor(color.darker());
        g.drawLine(x + 1, y + squareHeight() - 1,  x + squareWidth() - 1, y + squareHeight() - 1);
        g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1);
    }

    public void paint(Graphics g){
        super.paint(g);
        Dimension size = getSize();
        int top = (int) size.getHeight() - HEIGHT * squareHeight();
        for(int i=0; i<HEIGHT; i++){
            for(int j=0; j<WIDTH; j++){
                String s = shapeAt(j, HEIGHT-i-1);
                if(!s.equals(Shape.DEFAULTSHAPE))
                    drawSquare(g, j * squareWidth(), top + i * squareHeight(), s);
            }
        }
        if(!block.getShape().equals(Shape.DEFAULTSHAPE)){
            for(int i=0; i<4; i++){
                int x = coordX + block.x(i);
                int y = coordY - block.y(i);
                drawSquare(g, x * squareWidth(), top + (HEIGHT - y - 1) * squareHeight(), block.getShape());
            }
        }
    }

    public void start(){
        if(isPaused) return;
        isRunning = true;
        isFinishedFalling = false;
        score = 0;
        clearShapes();
        newBlock();
        timer.start();
    }

    private void pause(){
        if(!isRunning) return;
        isPaused = !isPaused;
        if(isPaused){
            timer.stop();

        } else{
            timer.start();

        }
        repaint();
    }

    class MAdapter extends MouseAdapter{
        public void mouseClicked(MouseEvent e){
            if (!isRunning || block.getShape().equals(Shape.DEFAULTSHAPE) || isPaused) return;
            int buttonPressed = e.getButton();
            if(buttonPressed == MouseEvent.BUTTON1) moveLeft();
            if(buttonPressed == MouseEvent.BUTTON2) rotate();
            if(buttonPressed == MouseEvent.BUTTON3) moveRight();
        }
    }

    class WheelAdapter implements MouseWheelListener{
        public void mouseWheelMoved(MouseWheelEvent e){
            if (!isRunning || block.getShape().equals(Shape.DEFAULTSHAPE) || isPaused) return;
            int wheelRotation = e.getWheelRotation();
            if(wheelRotation == 1) moveDown();
            if(wheelRotation == -1) dropDown();
        }
    }

    class KAdapter extends KeyAdapter{
        public void keyPressed(KeyEvent e){
            if (!isRunning || block.getShape().equals(Shape.DEFAULTSHAPE) || isPaused) return;
            int key = e.getKeyCode();
            if(key == KeyEvent.VK_SPACE) pause();
        }
    }
}

My problem is the following: When I try to rotate the blocks, it works good the first time, but it messes up completely if I try to rotate them a second time.

This is supposed to be my line shape:

line

And this is my L shape (the yellow one):

l

Note that this isn't just a graphical bug, the game treats the elements as one single square, or two respectively. I've been looking for hours at my code to see what the problem could be, but I had no luck. Any help would be appreciated

2

There are 2 answers

0
V Sebi On

Thank you for the replies, but after further investigation, I found out what the problem was. The problem was with the rotate method:

public Shape rotate(){
        if (this.getShape().equals(SQUARESHAPE)) return this;
        Shape newShape = new Shape();
        newShape.shape = this.shape;
        for (int i = 0; i < 4; i++) {
            newShape.setX(i, -y(i));
            newShape.setY(i, x(i));
        }
        return newShape;
}

I added to the mouse adapter the following code to see what happens with the coordinates of the current block:

if(buttonPressed == MouseEvent.BUTTON2) {
                System.out.println(Arrays.deepToString(block.getCoords()));
                rotate();
            }

This is the output for the SShape:

[[0, -1], [0, 0], [1, 0], [1, 1]]
[[1, 0], [0, 0], [0, 1], [-1, 1]]
[[0, 0], [0, 0], [-1, -1], [-1, -1]]
[[0, 0], [0, 0], [1, 1], [1, 1]]
[[0, 0], [0, 0], [-1, -1], [-1, -1]]
[[0, 0], [0, 0], [1, 1], [1, 1]]
[[0, 0], [0, 0], [-1, -1], [-1, -1]]

The first line contains the initial coordinates I gave for the SShape. The second line contains the modified coordinates, after the rotate() method. As you can see, X takes the -Y value and Y takes the X value. In the third line however, X takes the -Y value, but Y takes the updated X value instead of the previous one, so X = Y from the third line onwards. To solve this problem, I made an array to hold the values of X before updating it, as follows:

public Shape rotate(){
        if (this.getShape().equals(SQUARESHAPE)) return this;
        Shape newShape = new Shape();
        newShape.shape = this.shape;
        int[] oldX = {this.x(0), this.x(1), this.x(2), this.x(3)};
        for (int i = 0; i < 4; i++) {
            newShape.setX(i, -y(i));
            newShape.setY(i, oldX[i]);
        }
        return newShape;
}
0
Shaun On

Remove the rotate method, add a hard-coded rotation array each shape and all 4 rotations. First index should be the rotation index (0-3)

then add a member variable to the shape base class and change rotation to something like:

public void rotate(Boolean rotateRight) {
  if (rotateRight) {
    rotation++;
  else { 
    rotation--;
  }
  if (rotation < 0) {
    rotation = 3;
  }
  if (rotation > 3) {
    rotation = 0;
  }
}

Then make some new function like

public int[][] getCurrentRotation() {
  return shapeMatrix[rotation];
}

and use this int[][] (or int[] if you want to flatten the array) to draw the appropriate squares