기본 자바 테트리스 게임
저는 학교에서 정말 기본적인 자바를 배우고 있지만 집에서 혼자 배웁니다. 나는 간단한 스윙 게임에 대한 경험이 있지만 이것은 모든 것을 능가합니다. 누군가가 기여할 수있는 의견과 조언을 원합니다.
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;
}
}
}
답변
귀하의 코드에 대한 몇 가지 제안이 있습니다.
loop
&에 항상 중괄호를 추가하십시오.if
제 생각에는 중괄호로 둘러싸 지 않은 코드 블록을 갖는 것은 나쁜 습관입니다. 제 경력에서 그와 관련된 버그를 너무 많이 보았습니다. 코드를 추가 할 때 중괄호를 추가하는 것을 잊으면 코드의 논리 / 의미가 깨집니다.
여러 번 사용할 경우 표현식을 변수로 추출하십시오.
코드에서 표현식을 변수로 추출 할 수 있습니다. 이렇게하면 코드가 더 짧고 읽기 쉬워집니다.
전에
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();
후
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();
}
코드에 이와 같은 다른 경우가 있습니다. 동일한 작업을 수행하는 것이 좋습니다 ( new Dimension(width, height)
, 요법).
반환하거나받을 때 항상 배열의 복사본을 사용합니다.
Java의 대부분의 컨테이너 (Map, List, Arrays)는 변경 가능합니다 (일부 구현 제외). getter에서 인스턴스를 반환하면 이에 대한 액세스 권한이있는 모든 클래스가 컬렉션을 수정할 수 있습니다. 이런 식으로 자신의 데이터를 제어 할 수 없게됩니다. 이를 극복하려면 배열의 새 복사본을 만들고 컬렉션을 수정할 수없는 구현으로 변환 한 다음 값을 반환해야합니다.
항상 자신의 데이터에 대한 제어권을 유지하고 컬렉션을 다른 사람들과 직접 공유하지 말고 컬렉션 / 배열을받을 때 데이터를 내부 컬렉션에 복사하십시오.
전에
public int[] coordinates() {
return coords;
}
후
public int[] coordinates() {
return Arrays.copyOf(coords, coords.length);
}
배열 을 복사하는 여러 가지 방법이 있습니다.
정적 변수 대신 getter를 사용합니다.
에서 Board
클래스, 당신은 가치를 공유하는 정적 변수를 사용; 이것은 버그이자 나쁜 습관입니다. 정적 변수는 인스턴스간에 공유되기 때문입니다 (보드의 여러 인스턴스를 생성하면 모든 인스턴스에서 값이 변경됨). 대신 데이터를 숨기고 getter를 생성하는 것이 좋습니다.
for
루프를 향상된 'for'루프로 교체
코드에서 루프에서 제공하는 인덱스가 실제로 필요하지 않으며 향상된 버전을 사용할 수 있습니다.
전에
for (int i = 0; i < coords.length; i++) {
//[...]
}
후
for (int coord : coords) {
}