I am creating a tetris clone as a personal project to help me get better at drawing images, moving them, and to learn collision detection.
Everything is going fine but I
I just can't figure out how to loop it so that once that shape stops moving it adds another shape at the top of the screen.
Well in your Timer logic you have:
component.moveRectangleBy(dx,dy);
So this assumes you always have an "active" component. You need the ability to determine when the component is at the bottom so you can reset the component.
So you might restructure your listener code to look something like:
if (component == null)
component = // a new random shape
else
{
component.moveRectangleBy(...);
if (component.isAtBottom()) // a method you will need to write
component == null;
}
For what its worth, I have also played with my own Tetris game. It currently:
The basic design is in 4 classes:
If you want to play with it, have fun:
Tetris:
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.awt.geom.*;
public class Tetris extends JPanel
{
private final static int TETRIS_ICON_SIZE = 20;
private List<TetrisPiece> tetrisPieces = new ArrayList<TetrisPiece>();
private TetrisBoard board;
private Random random = new Random();
public Tetris()
{
setLayout( new BorderLayout() );
createTetrisPieces();
board = new TetrisBoard(20, 10, 20);
add(board, BorderLayout.LINE_START);
/*
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 5, 5);
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 5, 6);
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 6, 5);
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 0, 0);
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 0, 19);
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 9, 0);
board.setTetrisIconAt(new TetrisIcon(Color.RED, TETRIS_ICON_SIZE), 9, 19);
board.setTetrisPiece( tetrisPieces.get(1) );
*/
JButton start = new JButton( new StartAction() );
add(start, BorderLayout.PAGE_END);
}
private void createTetrisPieces()
{
int[][] shape =
{
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.RED, TETRIS_ICON_SIZE)) );
shape = new int[][]
{
{0, 1, 0, 0},
{0, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.YELLOW, TETRIS_ICON_SIZE)) );
shape = new int[][]
{
{0, 0, 1, 0},
{0, 0, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.MAGENTA, TETRIS_ICON_SIZE)) );
shape = new int[][]
{
{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.CYAN, TETRIS_ICON_SIZE)) );
shape = new int[][]
{
{0, 0, 0, 0},
{0, 1, 0, 0},
{1, 1, 1, 0},
{0, 0, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.WHITE, TETRIS_ICON_SIZE)) );
shape = new int[][]
{
{0, 0, 0, 0},
{0, 1, 1, 0},
{1, 1, 0, 0},
{0, 0, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.BLUE, TETRIS_ICON_SIZE)) );
shape = new int[][]
{
{0, 0, 0, 0},
{1, 1, 0, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
};
tetrisPieces.add( new TetrisPiece(shape, new TetrisIcon(Color.GREEN, TETRIS_ICON_SIZE)) );
}
class StartAction extends AbstractAction
{
public StartAction()
{
super("Start Game");
}
@Override
public void actionPerformed(ActionEvent e)
{
new Timer(1000, new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e2)
{
if (board.getTetrisPiece() == null)
{
int piece = random.nextInt( tetrisPieces.size() );
board.setTetrisPiece( tetrisPieces.get( piece ) );
}
else
{
board.moveShapeDown();
}
}
}).start();
}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("Tetris");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Tetris());
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
TetrisBoard:
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;
class TetrisBoard extends JPanel
{
private List<TetrisIcon[]> board;
private int rows;
private int columns;
private int size;
private TetrisPiece tetrisPiece;
public TetrisBoard(int rows, int columns, int size)
{
this.rows = rows;
this.columns = columns;
this.size = size;
board = new ArrayList<TetrisIcon[]>(rows);
for (int i = 0; i < rows; i++)
board.add( new TetrisIcon[columns] );
setBackground( Color.BLACK );
addKeyBindings();
}
private void addKeyBindings()
{
InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
String leftName = "LEFT";
KeyStroke leftKeyStroke = KeyStroke.getKeyStroke( leftName );
inputMap.put(leftKeyStroke, leftName);
actionMap.put(leftName, new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
moveShapeLeft();
}
});
String rightName = "RIGHT";
KeyStroke rightKeyStroke = KeyStroke.getKeyStroke( rightName );
inputMap.put(rightKeyStroke, rightName);
actionMap.put(rightName, new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
moveShapeRight();
}
});
String downName = "DOWN";
KeyStroke downKeyStroke = KeyStroke.getKeyStroke( downName );
inputMap.put(downKeyStroke, downName);
actionMap.put(downName, new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
// moveShapeDown();
dropShape();
}
});
String upName = "UP";
KeyStroke upKeyStroke = KeyStroke.getKeyStroke( upName );
inputMap.put(upKeyStroke, upName);
actionMap.put(upName, new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent e)
{
rotateShape();
}
});
}
public TetrisPiece getTetrisPiece()
{
return tetrisPiece;
}
public void setTetrisPiece(TetrisPiece tetrisPiece)
{
this.tetrisPiece = new TetrisPiece(tetrisPiece.getShape(), tetrisPiece.getIcon());
this.tetrisPiece.setLocation( new Point(4, 0) );
repaint();
}
public void setTetrisIconAt(TetrisIcon icon, int x, int y)
{
TetrisIcon[] row = board.get(y);
row[x] = icon;
}
public TetrisIcon getTetrisIconAt(int x, int y)
{
TetrisIcon[] row = board.get(y);
return row[x];
}
public void moveShapeLeft()
{
if (tetrisPiece == null) return;
Point possibleLocation = new Point(tetrisPiece.getX() - 1, tetrisPiece.getY());
if ( canMoveShape(possibleLocation, tetrisPiece.getShape()) )
{
tetrisPiece.setLocation( possibleLocation );
repaint();
}
}
public void moveShapeRight()
{
if (tetrisPiece == null) return;
Point possibleLocation = new Point(tetrisPiece.getX() + 1, tetrisPiece.getY());
if ( canMoveShape(possibleLocation, tetrisPiece.getShape()) )
{
tetrisPiece.setLocation( possibleLocation );
repaint();
}
}
public void dropShape()
{
if (tetrisPiece == null) return;
Point possibleLocation = new Point(tetrisPiece.getX(), tetrisPiece.getY() + 1);
while ( canMoveShape(possibleLocation, tetrisPiece.getShape()) )
{
moveShapeDown();
possibleLocation = new Point(tetrisPiece.getX(), tetrisPiece.getY() + 1);
}
// addTetrisPieceToBoard();
// tetrisPiece = null;
}
public void moveShapeDown()
{
if (tetrisPiece == null) return;
Point possibleLocation = new Point(tetrisPiece.getX(), tetrisPiece.getY() + 1);
if ( canMoveShape(possibleLocation, tetrisPiece.getShape()) )
{
tetrisPiece.setLocation( possibleLocation );
repaint();
}
else
{
tetrisPieceAtBottom();
}
}
private void tetrisPieceAtBottom()
{
Point location = tetrisPiece.getLocation();
int row = Math.min(rows, location.y + 4);
row--;
addTetrisPieceToBoard();
int rowsRemoved = 0;
for (; row >= location.y; row--)
{
// System.out.println(row);
TetrisIcon[] icons = board.get(row);
if ( fullRow(row) )
{
board.remove(row);
rowsRemoved++;
}
}
for (int i = 0; i < rowsRemoved; i++)
board.add(0, new TetrisIcon[columns]);
if (rowsRemoved > 0)
repaint();
}
private boolean fullRow(int row)
{
for (int column = 0; column < columns; column++)
{
// System.out.println(row + " : " + column);
if ( getTetrisIconAt(column, row) == null)
return false;
}
return true;
}
private void addTetrisPieceToBoard()
{
int x = tetrisPiece.getX();
int y = tetrisPiece.getY();
for (int r = 0; r < tetrisPiece.getRows(); r++)
{
for (int c = 0; c < tetrisPiece.getColumns(); c++)
{
TetrisIcon icon = tetrisPiece.getIconAt(r, c);
if (icon != null)
{
setTetrisIconAt(icon, x, y);
}
x++;
}
x = tetrisPiece.getX();
y++;
}
tetrisPiece = null;
}
public void rotateShape()
{
if (tetrisPiece == null) return;
int[][] rotatedShape = tetrisPiece.getRotatedShape();
if ( canMoveShape(tetrisPiece.getLocation(), rotatedShape) )
{
tetrisPiece.setShape( rotatedShape );
repaint();
}
}
private boolean canMoveShape(Point location, int[][] shape)
{
for (int r = 0; r < shape.length; r ++)
{
for (int c = 0; c < shape.length; c++)
{
if (shape[r][c] == 1)
{
int x = location.x + c;
int y = location.y + r;
// Past left edge
if (x < 0) return false;
// Past right edge
if (x >= columns) return false;
// Past bottom edge
if (y >= rows) return false;
// Collision with TetrisIcon
if (getTetrisIconAt(x, y) != null) return false;
}
}
}
return true;
}
@Override
public Dimension getPreferredSize()
{
int width = (columns * size) + columns - 1;
int height = (rows * size) + rows - 1;
return new Dimension(width, height);
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent( g );
int x = 0;
int y = 0;
int offset = size + 1;
for (int r = 0; r < rows; r++)
{
TetrisIcon[] row = board.get(r);
for (int c = 0; c < row.length; c++)
{
TetrisIcon icon = row[c];
if (icon != null)
{
icon.paintIcon(this, g, x, y);
}
x += offset;
}
x = 0;
y += offset;
}
// paint shape
if (tetrisPiece != null)
{
paintShape(g, offset);
}
}
private void paintShape(Graphics g, int offset)
{
int x = tetrisPiece.getX() * offset;
int y = tetrisPiece.getY() * offset;
for (int r = 0; r < tetrisPiece.getRows(); r++)
{
for (int c = 0; c < tetrisPiece.getColumns(); c++)
{
TetrisIcon icon = tetrisPiece.getIconAt(r, c);
if (icon != null)
{
icon.paintIcon(this, g, x, y);
}
x += offset;
}
x = tetrisPiece.getX() * offset;
y += offset;
}
}
}
TetrisPiece:
import java.awt.Point;
public class TetrisPiece
{
private int[][] shape;
private TetrisIcon icon;
private Point location = new Point();
public TetrisPiece(int[][] shape, TetrisIcon icon)
{
setShape(shape);
this.icon = icon;
}
public TetrisIcon getIcon()
{
return icon;
}
public int[][] getShape()
{
return shape;
}
public void setShape(int[][] shape)
{
this.shape = shape;
}
public TetrisIcon getIconAt(int x, int y)
{
return (shape[x][y] == 1) ? icon : null;
}
public Point getLocation()
{
return location;
}
public void setLocation(Point location)
{
this.location = location;
}
public int getX()
{
return location.x;
}
public int getY()
{
return location.y;
}
public int getRows()
{
return shape.length;
}
public int getColumns()
{
return shape[0].length;
}
public int[][] getRotatedShape()
{
int[][] rotatedShape = new int[shape.length][shape[0].length];
int x = 0;
int y = 0;
for (int c = shape.length - 1; c >= 0; c--)
{
for (int r = 0; r < shape[0].length; r++)
{
rotatedShape[x][y] = shape[r][c];
y++;
}
x++;
y = 0;
}
return rotatedShape;
}
}
TetrisIcon:
import java.awt.*;
import javax.swing.*;
public class TetrisIcon implements Icon
{
private Color color;
private int size;
public TetrisIcon(Color color, int size)
{
this.color = color;
this.size = size;
}
public int getIconWidth()
{
return size;
}
public int getIconHeight()
{
return size;
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
int width = getIconWidth() - 1;
int height = getIconHeight() - 1;
g.translate(x, y);
g.setColor(color);
g.fillRect(0, 0, width, height);
g.setColor(Color.LIGHT_GRAY);
g.drawLine(0, 0, width, 0);
g.drawLine(0, 0, 0, height);
g.setColor(Color.DARK_GRAY);
g.drawLine(width, 0, width, height);
g.drawLine(0, height, width, height);
g.translate(-x, -y);
}
}