我设计了一个基本的俄罗斯方块游戏,并用Java语言实现了它。我将用它来演示软件工程和设计。我将用许多不同的编程语言,在不同的平台上重新实现它。因此,良好的概念是非常重要的。
目标:
穿孔(即更多的代码/抽象),KISS/YAGNI不太重要。
public class TetrisGame {
private final static Random random = new Random();
private final int blockSize = 30;
private final int columns = 11;
private final int rows = 16;
private boolean isRunning;
private static TetrisGame instance;
private final Board board;
private Tetromino fallingTetromino;
private final PeriodicTask gravity;
private final MainWindow window;
private TetrisGame(MainWindow window) {
this.window = window;
BoardView view = new BoardView(window, blockSize);
board = new Board(rows, columns, view);
gravity = new PeriodicTask(() -> {
boolean moved = fallingTetromino.moveDown();
if (!moved)
tetrominoCantMoveFurther();
}, 700);
}
public static TetrisGame createNew(MainWindow window) {
instance = new TetrisGame(window);
return instance;
}
public static TetrisGame getInstance() {
return instance;
}
public void start() {
isRunning = true;
generateNextTetromino();
gravity.start();
}
private void stop() {
isRunning = false;
fallingTetromino.dispose();
fallingTetromino = null;
gravity.stop();
}
public void handleCommand(UserCommand command) {
if (!isRunning)
return;
switch (command) {
case ROTATE:
fallingTetromino.rotateRight();
break;
case MOVE_LEFT:
fallingTetromino.moveLeft();
break;
case MOVE_DOWN:
if (fallingTetromino.moveDown())
gravity.reset();
else
tetrominoCantMoveFurther();
break;
case MOVE_RIGHT:
fallingTetromino.moveRight();
break;
case DROP:
fallingTetromino.drop();
tetrominoCantMoveFurther();
break;
}
}
public boolean canMoveTetrominoTo(Tetromino tetromino, int x, int y) {
return board.canAddTetromino(tetromino, x, y);
}
private void tetrominoCantMoveFurther() {
board.addTetromino(fallingTetromino);
board.removeFullRows();
generateNextTetromino();
}
private void generateNextTetromino() {
if (fallingTetromino != null)
fallingTetromino.dispose();
int type = random.nextInt(7);
TetrominoView view = new TetrominoView(window, blockSize);
Tetromino next = Tetromino.createAtCenter(type, view, columns);
fallingTetromino = next;
if (next != null)
gravity.reset();
else
stop();
}
}public class Board {
private final int rows;
private final int columns;
private SquareView[][] board;
private final BoardView view;
public Board(int rows, int cols, BoardView view) {
this.rows = rows;
this.columns = cols;
this.board = new SquareView[rows][cols];
this.view = view;
updateView();
}
public boolean canAddTetromino(Tetromino tetromino, int fromX, int fromY) {
SquareView[][] data = tetromino.getPolyominoData();
int height = data.length;
int width = data[0].length;
if (fromX < 0 || fromX + width > columns ||
fromY < 0 || fromY + height > rows)
return false;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if (data[i][j] != null && board[fromY + i][fromX + j] != null)
return false;
}
}
return true;
}
public void addTetromino(Tetromino tetromino) {
SquareView[][] data = tetromino.getPolyominoData();
int x = tetromino.getPosX();
int y = tetromino.getPosY();
int height = data.length;
int width = data[0].length;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if (data[i][j] != null)
board[y + i][x + j] = data[i][j];
}
}
updateView();
}
public void removeFullRows() {
boolean isRowFull;
for (int i = 0; i < rows; ++i) {
isRowFull = true;
for (int j = 0; j < columns && isRowFull; ++j) {
if (board[i][j] == null)
isRowFull = false;
}
if (isRowFull) {
for (int j = i; j > 0; --j)
System.arraycopy(board[j - 1], 0, board[j], 0, columns);
for (int j = 0; j < columns; ++j)
board[0][j] = null;
}
}
updateView();
}
private void updateView() {
view.update(board);
}
}public class Tetromino {
private SquareView[][][] partsData;
private TetrominoView view;
private int currentX = 0;
private int currentY = 0;
private int rotation;
private int width;
private int height;
private Tetromino(int type, TetrominoView view) {
this.view = view;
partsData = TetrominoDataSource.getData(type);
setRotation(0);
}
public static Tetromino createAtCenter(int type, TetrominoView view, int boardWidth) {
Tetromino tetromino = new Tetromino(type, view);
int x = (int) Math.ceil((boardWidth - tetromino.width) / 2);
boolean moved = tetromino.tryMove(x, 0);
if (!moved) {
tetromino.dispose();
return null;
}
return tetromino;
}
public void dispose() {
view.clear();
}
public SquareView[][] getPolyominoData() {
return partsData[rotation];
}
public int getPosX() {
return currentX;
}
public int getPosY() {
return currentY;
}
public boolean rotateRight() {
int nextRotation = (rotation + 1) % 4;
boolean canRotate = false;
int oldRotation = rotation;
setRotation(nextRotation);
if (canMoveTo(0, 0))
canRotate = true;
else {
for (int i = 1; i < width && !canRotate; ++i) {
if (canMoveTo(-i, 0)) {
currentX -= i;
canRotate = true;
}
}
}
if (!canRotate)
setRotation(oldRotation);
else {
setRotation(nextRotation);
updateView();
}
return canRotate;
}
public boolean moveRight() {
return tryMove(1, 0);
}
public boolean moveLeft() {
return tryMove(-1, 0);
}
public boolean moveDown() {
return tryMove(0, 1);
}
public void drop() {
boolean movedDown;
do {
movedDown = moveDown();
} while (movedDown);
}
private void setRotation(int rotation) {
this.rotation = rotation % partsData.length;
height = partsData[this.rotation].length;
width = partsData[this.rotation][0].length;
}
private void updateView() {
view.update(partsData[rotation], currentX, currentY);
}
private boolean tryMove(int x, int y) {
boolean canSlide = canMoveTo(x, y);
if (canSlide) {
currentX += x;
currentY += y;
updateView();
}
return canSlide;
}
private boolean canMoveTo(int deltaX, int deltaY) {
return TetrisGame.getInstance().canMoveTetrominoTo(this, currentX + deltaX, currentY + deltaY);
}
}public class TetrominoDataSource {
public static SquareView[][][] getData(int type) {
String[][] masks = rawData[type];
SquareView[][][] result = new SquareView[masks.length][][];
for (int rotation = 0; rotation < masks.length; ++rotation) {
int height = masks[rotation].length;
int width = masks[rotation][0].length();
result[rotation] = new SquareView[height][width];
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if (masks[rotation][i].charAt(j) != ' ')
result[rotation][i][j] = new SquareView(type);
}
}
}
return result;
}
private static String[][][] rawData = new String[][][] {
new String[][] {
new String[] {
"XX",
"XX"
}
},
new String[][] {
new String[] {
"X",
"X",
"X",
"X"
},
new String[] {
"XXXX"
}
},视图/TetrominoView.java (view/BoardView.java非常类似)
public class TetrominoView extends CanvasView {
public TetrominoView(MainWindow window, int squareSize) {
super(window.getTetrominoCanvas(), squareSize);
}
public void update(SquareView[][] data, int baseX, int baseY) {
clear();
int height = data.length;
int width = data[0].length;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if (data[i][j] != null)
data[i][j].update(context, baseX + j, baseY + i, squareSize);
}
}
}
}view:绘制一个填充的sqaure。
view:使用画布的助手类。
util:java.util.Timer的包装器。
window:将javafx.scene.input.KeyEvent转换为UserCommand。
JavaFX :管理JavaFX应用程序。
发布于 2018-07-18 19:40:11
单身通常是个坏主意,除非你有明确的理由。(它们几乎类似于全局变量。)在这种情况下,我看不出为什么TetrisGame应该是一个单例。事实上,如果你想让两个玩家同时玩游戏的话,你可能会想要实例化其中的两个,这似乎是很有道理的。
发布于 2018-07-23 09:05:36
我的朋友们也检查了我的代码,我发现了一些可以改进的地方:
NullPointerException (是的,有时我在创建原始版本时得到了它)。i和j的Iterator变量没有告诉它。为了解决这些空检查问题,我添加了两个辅助数据结构。Boad单元实现空对象模式。在Tetromino中,任何地方都不需要部件的顺序,所以(完整的)一维数组就足够了,助手结构存储偏移量(相对于tetromino位置的相对距离)。您可以查看更改这里。
generateNextTetromino中,也有一个空检查。我用的是工厂的方法。如果四分钟不能添加到董事会(即。),它返回null。创建一个新的对象不应该属于任何条件,这不是Tetromino的S的工作去处理自己。generateNextTetromino还包含与生成无关的逻辑,但包含它的结果。https://codereview.stackexchange.com/questions/199737
复制相似问题