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:
And this is my L shape (the yellow one):
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
Thank you for the replies, but after further investigation, I found out what the problem was. The problem was with the rotate method:
I added to the mouse adapter the following code to see what happens with the coordinates of the current block:
This is the output for the SShape:
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: