Juego básico de Java Tetris
Estoy aprendiendo Java realmente básico en la escuela, pero aprendo solo en casa. He tenido algo de experiencia con juegos de swing simples, pero este supera a todos. Quiero cualquier opinión y consejo que alguien pueda aportar.
public class Display {
private JFrame frame;
private Canvas canvas;
private String title;
private int width, height;
public Display(String title, int width, int height) {
this.title = title;
this.width = width;
this.height = height;
createDisplay();
}
private void createDisplay() {
frame = new JFrame(title);
frame.setSize(width, height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas = new Canvas();
canvas.setPreferredSize(new Dimension(width, height));
canvas.setMaximumSize(new Dimension(width, height));
canvas.setMinimumSize(new Dimension(width, height));
frame.add(canvas);
frame.pack();
}
public Canvas getCanvas() {
return canvas;
}
public void addKeyListner(KeyAdapter ka) {
canvas.addKeyListener(ka);
canvas.requestFocus();
}
}
public class Shape {
private int[] coords;
private int color;
private int pos;
public Shape(Shape shape) {
this(shape.coords, shape.color, shape.pos);
}
public Shape(int[] coords, int color) {
this(coords, color, 0);
}
public Shape(int[] coords, int color, int pos) {
this.coords = coords;
this.color = color;
this.pos = pos;
}
public void rotate() {
pos++;
if (pos == 4) pos = 0;
}
public int color() {
return color;
}
public int position() {
return pos;
}
public int[] coordinates() {
return coords;
}
}
public class Game implements Runnable {
private Display display;
private Board board;
private int width, height;
private String title;
private boolean running = false;
private Thread gameThread;
private int tickTime = 400;
private BufferStrategy bs;
private Graphics g;
private KeyKeeper keyKeeper;
public Game(String title, int width, int height) {
this.width = width;
this.height = height;
this.title = title;
}
private void initTick() {
while (running) {
try {
gameThread.sleep(tickTime);
} catch (InterruptedException ie) {}
tick();
}
}
private void init() {
display = new Display(title, width, height);
board = new Board(width - 100, height);
keyKeeper = new KeyKeeper();
display.addKeyListner(keyKeeper);
}
private void tick() {
board.tick();
}
private void render() {
bs = display.getCanvas().getBufferStrategy();
if (bs == null) {
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Draw Here!
//background
Tetris.drawBackground(g, board, 0, 0);
// board
Tetris.drawBoard(g, board, 0, 0);
//shape
Tetris.drawShape(g, board);
//End Drawing!
bs.show();
g.dispose();
}
public void run() {
init();
while (running) {
render();
}
stop();
}
public synchronized void start() {
if (running) {
return;
}
running = true;
gameThread = new Thread(this);
gameThread.start();
initTick();
}
public synchronized void stop() {
if (!running) {
return;
}
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class KeyKeeper extends KeyAdapter {
@Override
public void keyPressed(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
board.moveShape(-1, 0);
} else if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
board.moveShape(1, 0);
} else if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
board.moveShape(0, 1);
} else if (ke.getKeyCode() == KeyEvent.VK_UP) {
board.hardDown();
}
else if (ke.getKeyCode() == KeyEvent.VK_SPACE)
board.rotateShape();
}
}
}
public class Board {
public static int width, height;
public static int tx, ty;
public static int xts;
public static int yts;
private int[][] boardCoor;
private int[][] coords;
private Shape noShape;
private Point shapeCoorPoint;
private boolean[] shapeUsed;
private int shapeCounter;
public Board(int width, int height) {
this.width = width;
this.height = height;
init();
}
private void init() {
tx = 12;
ty = 24;
xts = width / tx;
yts = height / ty;
boardCoor = new int[tx][ty];
for (int i = 0; i < ty; i++) {
for (int j = 0; j < tx; j++) {
boardCoor[j][i] = 7;
}
}
coords = new int[][]{
{0, 2, 4, 6},// I
{1, 2, 3, 4},// Z
{0, 2, 3, 5},// S
{0, 2, 3, 4},// T
{0, 2, 4, 5},// L
{1, 3, 5, 4},// J
{2, 3, 4, 5} // O
};
shapeCoorPoint = new Point();
shapeUsed = new boolean[]{false, false, false, false, false, false, false};
shapeCounter = 0;
initShape();
}
public int[][] getBoard() {
return boardCoor;
}
public Shape getShape() {
return noShape;
}
public Point getShapeCoorPoint() {
return shapeCoorPoint;
}
private void initShape() {
boolean changeShape = true;
int n;
while (changeShape) {
n = (int) (Math.random() * 7);
if (!shapeUsed[n]) {
noShape = new Shape(coords[n], n);
shapeUsed[n] = true;
shapeCounter++;
changeShape = false;
}
}
if (shapeCounter == 7) {
shapeUsed = new boolean[]{false, false, false, false, false, false, false};
shapeCounter = 0;
}
shapeCoorPoint.move(tx / 2 - 1, 0);
}
public void tick() {
if (Tetris.canFall(this)) {
shapeCoorPoint.translate(0, 1);
} else {
Tetris.update(this);
clearLines();
initShape();
}
}
public boolean moveShape(int dx, int dy) {
//dy=1 - down
//dx=-1 - right
//dx=1 - left
// ~~~ strategy ~~~
// create an instance point, then, check -
//if legal, translate the shape point.
Point instancePoint = new Point(shapeCoorPoint);
instancePoint.translate(dx, dy);
if (Tetris.isLegal(boardCoor, noShape, instancePoint)) {
shapeCoorPoint.translate(dx, dy);
return true;
}
return false;
}
public void hardDown() {
boolean stop;
do {
stop = moveShape(0, 1);
} while (stop);
}
public boolean rotateShape() {
//~~~ strategy ~~~
//create an instance shape, then, check -
//if legal, rotate
Shape instanceShape = new Shape(noShape);
instanceShape.rotate();
if (Tetris.isLegal(boardCoor, instanceShape, shapeCoorPoint)) {
noShape.rotate();
return true;
}
return false;
}
private void clearLines() {
boolean isFilled;
for (int row = 0; row < ty; row++) {
isFilled = true;
//check the first tile of the each rank
for (int col = 0; col < tx; col++) {
if (boardCoor[col][row] == 7) {
isFilled = false;
col = tx;
}
}
if (isFilled) {
for (int i = 0; i < tx; i++) {
for (int j = row; j > 0; j--) {
boardCoor[i][j] = boardCoor[i][j - 1];
boardCoor[i][j - 1] = 7;
}
}
}
}
}
}
public class Tetris {
//~~~graphic drawings~~~
public static void drawBackground(Graphics g, Board board, int x, int y) {
g.setColor(Color.black);
g.fillRect(x, y, board.width, board.height);
g.setColor(Color.white);
g.drawRect(x, y, board.width, board.height);
g.setColor(Color.gray);
for (int i = 1; i < board.ty; i++) {
g.drawLine(x, y + i * board.yts,
x + board.width, y + i * board.yts);
}
for (int i = 1; i < board.tx; i++) {
g.drawLine(x + i * board.xts, y,
x + i * board.xts, y + board.height);
}
}
public static void drawBoard(Graphics g, Board board, int x, int y) {
int[][] boardCoor = board.getBoard();
int c;
Color[] colors = new Color[]{
Color.red, Color.blue, Color.orange, Color.magenta,
Color.cyan, Color.green, Color.yellow, Color.black};
for (int i = 0; i < board.ty; i++) {
for (int j = 0; j < board.tx; j++) {
c = boardCoor[j][i];
g.setColor(colors[c]);
g.fillRect(x + j * board.xts + 1, y + i * board.yts + 1,
board.xts - 1, board.yts - 1);
}
}
}
public static void drawShape(Graphics g, Board board) {
Point point = board.getShapeCoorPoint();
Shape shape = board.getShape();
int[] coords = shape.coordinates();
int pos = shape.position();
int c = shape.color();
Color[] colors = new Color[]{
Color.red, Color.blue, Color.orange, Color.magenta,
Color.cyan, Color.green, Color.yellow, Color.black};
g.setColor(colors[c]);
int[] arr;
for (int i = 0; i < coords.length; i++) {
arr = getXY(coords[i], pos, point);
g.fillRect(
(arr[0]) * board.xts + 1, (arr[1]) * board.yts + 1,
board.xts - 1, board.yts - 1);
}
}
// ~~~game rules~~~
public static boolean canFall(Board board) {
return canFall(board.getBoard(), board.getShape(), board.getShapeCoorPoint());
}
public static boolean canFall(int[][] boardCoor, Shape shape, Point point) {
return canFall(boardCoor, shape.coordinates(), shape.position(), point);
}
public static boolean canFall(int[][] boardCoor, int[] coords, int pos, Point point) {
int[] arr;
for (int i = 0; i < coords.length; i++) {
arr = getXY(coords[i], pos, point);
if (arr[1] == Board.ty - 1 || boardCoor[arr[0]][arr[1] + 1] != 7) {
return false;
}
}
return true;
}
public static boolean isLegal(Board board) {
return isLegal(board.getBoard(), board.getShape(), board.getShapeCoorPoint());
}
public static boolean isLegal(int[][] boardCoor, Shape shape, Point point) {
return isLegal(boardCoor, shape.coordinates(), shape.position(), point);
}
public static boolean isLegal(int[][] boardCoor, int[] coords, int pos, Point point) {
int[] arr;
for (int i = 0; i < coords.length; i++) {
arr = getXY(coords[i], pos, point);
if (arr[1] >= Board.ty || arr[1] < 0 ||
arr[0] < 0 || arr[0] >= Board.tx ||
boardCoor[arr[0]][arr[1]] != 7) {
return false;
}
}
return true;
}
//~~~technical functions~~~
public static void update(Board board) {
update(board.getBoard(), board.getShape(), board.getShapeCoorPoint());
}
public static void update(int[][] boardCoor, Shape shape, Point point) {
update(boardCoor, shape.coordinates(), shape.color(), shape.position(), shape, point);
}
public static void update(int[][] boardCoor, int[] coords, int color, int pos, Shape shape, Point point) {
int[] arr;
for (int i = 0; i < coords.length; i++) {
arr = getXY(coords[i], pos, point);
boardCoor[arr[0]][arr[1]] = color;
}
}
private static int[] getXY(int value, int pos, Point point) {
int[] arr = new int[2];
if (pos == 0) {
arr[0] = value % 2 + point.x;
arr[1] = value / 2 + point.y;
return arr;
} else if (pos == 1) {
arr[0] = 2 - value / 2 + point.x;
arr[1] = 1 + value % 2 + point.y;
return arr;
} else if (pos == 2) {
arr[0] = 1 - value % 2 + point.x;
arr[1] = 3 - value / 2 + point.y;
return arr;
} else {
arr[0] = value / 2 - 1 + point.x;
arr[1] = 2 - value % 2 + point.y;
return arr;
}
}
}
Respuestas
Tengo algunas sugerencias para tu código.
Siempre agregue llaves a loop
&if
En mi opinión, es una mala práctica tener un bloque de código que no esté rodeado por llaves; Vi tantos errores en mi carrera relacionados con eso, si te olvidas de agregar las llaves al agregar código, rompes la lógica / semántica del código.
Extrae la expresión a variables cuando se usa varias veces.
En su código, puede extraer la expresión en variables; esto hará que el código sea más corto y más fácil de leer.
antes de
if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
board.moveShape(-1, 0);
} else if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
board.moveShape(1, 0);
} else if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
board.moveShape(0, 1);
} else if (ke.getKeyCode() == KeyEvent.VK_UP) {
board.hardDown();
} else if (ke.getKeyCode() == KeyEvent.VK_SPACE)
board.rotateShape();
Después
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT) {
board.moveShape(-1, 0);
} else if (keyCode == KeyEvent.VK_RIGHT) {
board.moveShape(1, 0);
} else if (keyCode == KeyEvent.VK_DOWN) {
board.moveShape(0, 1);
} else if (keyCode == KeyEvent.VK_UP) {
board.hardDown();
} else if (keyCode == KeyEvent.VK_SPACE) {
board.rotateShape();
}
Hay otros casos como este en su código, le sugiero que haga lo mismo con ellos ( new Dimension(width, height)
, ect).
Siempre usa una copia de la matriz al devolverla o recibirla
La mayoría de los contenedores (Map, List, Arrays) en java son mutables (excepción para algunas implementaciones). Si devuelve una instancia en un captador, cualquier clase que tenga acceso a ella puede modificar la colección; pierde el control de sus propios datos de esta manera. Para superar eso, debe crear una nueva copia de la matriz / convertir la colección a una implementación que no se pueda modificar y luego devolver el valor.
Siempre trate de mantener el control sobre sus propios datos, nunca comparta las colecciones directamente con los demás y cuando reciba una colección / matriz, copie los datos en sus colecciones internas.
antes de
public int[] coordinates() {
return coords;
}
Después
public int[] coordinates() {
return Arrays.copyOf(coords, coords.length);
}
Son varias formas de copiar una matriz .
Utiliza captadores en lugar de variables estáticas
En la Board
clase, usas variables estáticas para compartir los valores; esto es un error y un mal hábito, ya que las variables estáticas se compartirán entre instancias (si crea múltiples instancias del tablero, los valores se cambiarán en todas las instancias). En cambio, le sugiero que oculte los datos y cree captadores para ellos.
Reemplazar el for
bucle con un bucle mejorado 'for'
En su código, en realidad no necesita el índice proporcionado por el bucle, puede usar la versión mejorada.
antes de
for (int i = 0; i < coords.length; i++) {
//[...]
}
Después
for (int coord : coords) {
}