Game Java Tetris dasar
Saya belajar bahasa Jawa yang sangat dasar di sekolah tetapi saya belajar sendiri di rumah. Saya memiliki pengalaman dengan permainan ayunan sederhana, tapi yang ini melebihi segalanya. Saya ingin pendapat dan saran yang dapat diberikan seseorang.
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;
}
}
}
Jawaban
Saya punya beberapa saran untuk kode Anda.
Selalu tambahkan kurung kurawal ke loop
&if
Menurut pendapat saya, adalah praktik yang buruk untuk memiliki blok kode yang tidak dikelilingi oleh tanda kurung kurawal; Saya melihat begitu banyak bug dalam karir saya terkait dengan itu, jika Anda lupa menambahkan tanda kurung saat menambahkan kode, Anda merusak logika / semantik kode.
Ekstrak ekspresi ke variabel saat digunakan beberapa kali.
Dalam kode Anda, Anda dapat mengekstrak ekspresi menjadi variabel; ini akan membuat kode lebih pendek dan lebih mudah dibaca.
Sebelum
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();
Setelah
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();
}
Ada kasus lain seperti ini di kode Anda, saya sarankan Anda melakukan hal yang sama untuk mereka ( new Dimension(width, height)
, dll).
Selalu menggunakan salinan larik saat mengembalikan atau menerimanya
Sebagian besar container (Map, List, Array) di java bisa berubah (pengecualian untuk beberapa implementasi). Jika Anda mengembalikan sebuah instance dalam getter, kelas apa pun yang memiliki akses ke sana dapat mengubah koleksi; Anda kehilangan kendali atas data Anda sendiri dengan cara ini. Untuk mengatasinya, Anda perlu membuat salinan baru dari larik / mengonversi koleksi menjadi implementasi yang tidak dapat dimodifikasi dan kemudian, mengembalikan nilainya.
Selalu mencoba untuk tetap mengontrol data Anda sendiri, tidak pernah berbagi koleksi secara langsung dengan yang lain dan saat menerima koleksi / larik, salin data ke koleksi internal Anda.
Sebelum
public int[] coordinates() {
return coords;
}
Setelah
public int[] coordinates() {
return Arrays.copyOf(coords, coords.length);
}
Ada banyak cara untuk menyalin array .
Menggunakan getter, bukan variabel statis
Di Board
kelas, Anda menggunakan variabel statis untuk berbagi nilai; ini adalah bug dan kebiasaan buruk, karena variabel statis akan dibagikan ke semua instance (jika Anda membuat beberapa instance papan, nilainya akan berubah di semua instance). Sebagai gantinya, saya menyarankan Anda menyembunyikan data dan membuat getter untuk mereka.
Ganti for
loop dengan loop 'untuk' yang ditingkatkan
Dalam kode Anda, Anda tidak benar-benar membutuhkan indeks yang disediakan oleh loop, Anda dapat versi yang ditingkatkan.
Sebelum
for (int i = 0; i < coords.length; i++) {
//[...]
}
Setelah
for (int coord : coords) {
}