<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                {% raw %} # 俄羅斯方塊 > [http://zetcode.com/tutorials/javaswingtutorial/thetetrisgame/](http://zetcode.com/tutorials/javaswingtutorial/thetetrisgame/) 在本章中,我們將在 Java Swing 中創建一個俄羅斯方塊游戲克隆。 ## 俄羅斯方塊 俄羅斯方塊游戲是有史以來最受歡迎的計算機游戲之一。 原始游戲是由俄羅斯程序員 Alexey Pajitnov 于 1985 年設計和編程的。此后,幾乎所有版本的幾乎所有計算機平臺上都可以使用俄羅斯方塊。 俄羅斯方塊被稱為下降塊益智游戲。 在這個游戲中,我們有七個不同的形狀,稱為 tetrominoes:S 形,Z 形,T 形,L 形,線形,MirroredL 形和方形。 這些形狀中的每一個都形成有四個正方形。 形狀從板上掉下來。 俄羅斯方塊游戲的目的是移動和旋轉形狀,以使其盡可能地適合。 如果我們設法形成一行,則該行將被破壞并得分。 我們玩俄羅斯方塊游戲,直到達到頂峰。 ![Tetrominoes](https://img.kancloud.cn/2b/7a/2b7a874cd2ec9a34c259d3dd686809e9_328x132.jpg) 圖:Tetrominoes ## 開發 我們的俄羅斯方塊游戲沒有圖像,我們使用 Swing 繪圖 API 繪制四面體。 每個計算機游戲的背后都有一個數學模型。 俄羅斯方塊也是如此。 游戲背后的一些想法。 * 我們使用`Timer`類創建游戲周期 * 繪制四方塊 * 形狀以正方形為單位移動(不是逐個像素移動) * 從數學上講,棋盤是簡單的數字列表 游戲經過簡化,因此更易于理解。 游戲啟動后立即開始。 我們可以通過按 `p` 鍵暫停游戲。 `空格鍵`將俄羅斯方塊放到底部。 `d` 鍵將棋子下降一行。 (它可以用來加快下降速度。)游戲以恒定速度運行,沒有實現加速。 分數是我們已刪除的行數。 `com/zetcode/Tetris.java` ```java package com.zetcode; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JLabel; /* Java Tetris game clone Author: Jan Bodnar Website: http://zetcode.com */ public class Tetris extends JFrame { private JLabel statusbar; public Tetris() { initUI(); } private void initUI() { statusbar = new JLabel(" 0"); add(statusbar, BorderLayout.SOUTH); var board = new Board(this); add(board); board.start(); setTitle("Tetris"); setSize(200, 400); setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); } JLabel getStatusBar() { return statusbar; } public static void main(String[] args) { EventQueue.invokeLater(() -> { var game = new Tetris(); game.setVisible(true); }); } } ``` 在`Tetris`中,我們設置了游戲。 我們創建一個玩游戲的棋盤。 我們創建一個狀態欄。 ```java board.start(); ``` `start()`方法啟動俄羅斯方塊游戲。 `com/zetcode/Shape.java` ```java package com.zetcode; import java.util.Random; public class Shape { protected enum Tetrominoe { NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape } private Tetrominoe pieceShape; private int coords[][]; private int[][][] coordsTable; public Shape() { coords = new int[4][2]; setShape(Tetrominoe.NoShape); } void setShape(Tetrominoe shape) { coordsTable = new int[][][]{ {{0, 0}, {0, 0}, {0, 0}, {0, 0}}, {{0, -1}, {0, 0}, {-1, 0}, {-1, 1}}, {{0, -1}, {0, 0}, {1, 0}, {1, 1}}, {{0, -1}, {0, 0}, {0, 1}, {0, 2}}, {{-1, 0}, {0, 0}, {1, 0}, {0, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{-1, -1}, {0, -1}, {0, 0}, {0, 1}}, {{1, -1}, {0, -1}, {0, 0}, {0, 1}} }; for (int i = 0; i < 4; i++) { System.arraycopy(coordsTable[shape.ordinal()], 0, coords, 0, 4); } pieceShape = shape; } private void setX(int index, int x) { coords[index][0] = x; } private void setY(int index, int y) { coords[index][1] = y; } int x(int index) { return coords[index][0]; } int y(int index) { return coords[index][1]; } Tetrominoe getShape() { return pieceShape; } void setRandomShape() { var r = new Random(); int x = Math.abs(r.nextInt()) % 7 + 1; Tetrominoe[] values = Tetrominoe.values(); setShape(values[x]); } public int minX() { int m = coords[0][0]; for (int i = 0; i < 4; i++) { m = Math.min(m, coords[i][0]); } return m; } int minY() { int m = coords[0][1]; for (int i = 0; i < 4; i++) { m = Math.min(m, coords[i][1]); } return m; } Shape rotateLeft() { if (pieceShape == Tetrominoe.SquareShape) { return this; } var result = new Shape(); result.pieceShape = pieceShape; for (int i = 0; i < 4; ++i) { result.setX(i, y(i)); result.setY(i, -x(i)); } return result; } Shape rotateRight() { if (pieceShape == Tetrominoe.SquareShape) { return this; } var result = new Shape(); result.pieceShape = pieceShape; for (int i = 0; i < 4; ++i) { result.setX(i, -y(i)); result.setY(i, x(i)); } return result; } } ``` `Shape`提供有關俄羅斯方塊的信息。 ```java protected enum Tetrominoe { NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape } ``` `Tetrominoe`擁有七個俄羅斯方塊形狀名稱,而空的形狀稱為`NoShape`。 ```java public Shape() { coords = new int[4][2]; setShape(Tetrominoe.NoShape); } ``` 這是`Shape`類的構造器。 `coords`數組保存俄羅斯方塊的實際坐標。 ```java coordsTable = new int[][][] { { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, { { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } }, { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } }, { { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } }, { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } }, { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }, { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }, { { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } } }; ``` `coordsTable`數組保存我們的俄羅斯方塊的所有可能的坐標值。 這是一個模板,所有零件均從該模板獲取其坐標值。 ```java for (int i = 0; i < 4; i++) { System.arraycopy(coordsTable[shape.ordinal()], 0, coords, 0, 4); } ``` 在這里,我們將一行坐標值從`coordsTable`復制到俄羅斯方塊的`coords`數組。 請注意`ordinal()`方法的使用。 在 C++ 中,枚舉類型本質上是一個整數。 與 C++ 不同,Java 枚舉是完整類,`ordinal()`方法返回枚舉類型在枚舉對象中的當前位置。 ![Coordinates](https://img.kancloud.cn/5c/0b/5c0bbb2ed04c22b46915ea1ef803ce4d_272x230.jpg) 圖:坐標 該圖像有助于進一步了解坐標值。 `coords`數組保存俄羅斯方塊的坐標。 下圖說明了旋轉的 S 形。 它具有以下坐標:(-1,1),(-1,0),(0,0)和(0,-1)。 ```java Shape rotateRight() { if (pieceShape == Tetrominoe.SquareShape) { return this; } var result = new Shape(); result.pieceShape = pieceShape; for (int i = 0; i < 4; ++i) { result.setX(i, -y(i)); result.setY(i, x(i)); } return result; } ``` 此代碼將棋子向右旋轉。 正方形不必旋轉。 這就是為什么在`Tetrominoe.SquareShape`的情況下我們僅將引用返回給當前對象的原因。 查看上一張圖片將有助于進一步了解旋轉情況。 `com/zetcode/Board.java` ```java package com.zetcode; import com.zetcode.Shape.Tetrominoe; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.Timer; import java.awt.Color; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; public class Board extends JPanel implements ActionListener { private final int BOARD_WIDTH = 10; private final int BOARD_HEIGHT = 22; private Timer timer; private boolean isFallingFinished = false; private boolean isStarted = false; private boolean isPaused = false; private int numLinesRemoved = 0; private int curX = 0; private int curY = 0; private JLabel statusbar; private Shape curPiece; private Tetrominoe[] board; public Board(Tetris parent) { initBoard(parent); } private void initBoard(Tetris parent) { setFocusable(true); curPiece = new Shape(); int DELAY = 400; timer = new Timer(DELAY, this); timer.start(); statusbar = parent.getStatusBar(); board = new Tetrominoe[BOARD_WIDTH * BOARD_HEIGHT]; addKeyListener(new TAdapter()); clearBoard(); } @Override public void actionPerformed(ActionEvent e) { if (isFallingFinished) { isFallingFinished = false; newPiece(); } else { oneLineDown(); } } private int squareWidth() { return (int) getSize().getWidth() / BOARD_WIDTH; } private int squareHeight() { return (int) getSize().getHeight() / BOARD_HEIGHT; } private Tetrominoe shapeAt(int x, int y) { return board[(y * BOARD_WIDTH) + x]; } void start() { if (isPaused) { return; } isStarted = true; isFallingFinished = false; numLinesRemoved = 0; clearBoard(); newPiece(); timer.start(); } private void pause() { if (!isStarted) { return; } isPaused = !isPaused; if (isPaused) { timer.stop(); statusbar.setText("paused"); } else { timer.start(); statusbar.setText(String.valueOf(numLinesRemoved)); } repaint(); } private void doDrawing(Graphics g) { var size = getSize(); int boardTop = (int) size.getHeight() - BOARD_HEIGHT * squareHeight(); for (int i = 0; i < BOARD_HEIGHT; ++i) { for (int j = 0; j < BOARD_WIDTH; ++j) { Tetrominoe shape = shapeAt(j, BOARD_HEIGHT - i - 1); if (shape != Tetrominoe.NoShape) drawSquare(g, j * squareWidth(), boardTop + i * squareHeight(), shape); } } if (curPiece.getShape() != Tetrominoe.NoShape) { for (int i = 0; i < 4; ++i) { int x = curX + curPiece.x(i); int y = curY - curPiece.y(i); drawSquare(g, x * squareWidth(), boardTop + (BOARD_HEIGHT - y - 1) * squareHeight(), curPiece.getShape()); } } } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private void dropDown() { int newY = curY; while (newY > 0) { if (!tryMove(curPiece, curX, newY - 1)) { break; } --newY; } pieceDropped(); } private void oneLineDown() { if (!tryMove(curPiece, curX, curY - 1)) { pieceDropped(); } } private void clearBoard() { for (int i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; ++i) { board[i] = Tetrominoe.NoShape; } } private void pieceDropped() { for (int i = 0; i < 4; ++i) { int x = curX + curPiece.x(i); int y = curY - curPiece.y(i); board[(y * BOARD_WIDTH) + x] = curPiece.getShape(); } removeFullLines(); if (!isFallingFinished) { newPiece(); } } private void newPiece() { curPiece.setRandomShape(); curX = BOARD_WIDTH / 2 + 1; curY = BOARD_HEIGHT - 1 + curPiece.minY(); if (!tryMove(curPiece, curX, curY)) { curPiece.setShape(Tetrominoe.NoShape); timer.stop(); isStarted = false; statusbar.setText("game over"); } } private boolean tryMove(Shape newPiece, int newX, int newY) { for (int i = 0; i < 4; ++i) { int x = newX + newPiece.x(i); int y = newY - newPiece.y(i); if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return false; if (shapeAt(x, y) != Tetrominoe.NoShape) return false; } curPiece = newPiece; curX = newX; curY = newY; repaint(); return true; } private void removeFullLines() { int numFullLines = 0; for (int i = BOARD_HEIGHT - 1; i >= 0; --i) { boolean lineIsFull = true; for (int j = 0; j < BOARD_WIDTH; ++j) { if (shapeAt(j, i) == Tetrominoe.NoShape) { lineIsFull = false; break; } } if (lineIsFull) { ++numFullLines; for (int k = i; k < BOARD_HEIGHT - 1; ++k) { for (int j = 0; j < BOARD_WIDTH; ++j) board[(k * BOARD_WIDTH) + j] = shapeAt(j, k + 1); } } } if (numFullLines > 0) { numLinesRemoved += numFullLines; statusbar.setText(String.valueOf(numLinesRemoved)); isFallingFinished = true; curPiece.setShape(Tetrominoe.NoShape); repaint(); } } private void drawSquare(Graphics g, int x, int y, Tetrominoe shape) { Color colors[] = { new Color(0, 0, 0), new Color(204, 102, 102), new Color(102, 204, 102), new Color(102, 102, 204), new Color(204, 204, 102), new Color(204, 102, 204), new Color(102, 204, 204), new Color(218, 170, 0) }; var color = colors[shape.ordinal()]; g.setColor(color); g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2); g.setColor(color.brighter()); g.drawLine(x, y + squareHeight() - 1, x, y); g.drawLine(x, y, x + squareWidth() - 1, y); g.setColor(color.darker()); g.drawLine(x + 1, y + squareHeight() - 1, x + squareWidth() - 1, y + squareHeight() - 1); g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1, x + squareWidth() - 1, y + 1); } class TAdapter extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { if (!isStarted || curPiece.getShape() == Tetrominoe.NoShape) { return; } int keycode = e.getKeyCode(); if (keycode == 'P') { pause(); return; } if (isPaused) { return; } switch (keycode) { case KeyEvent.VK_LEFT: tryMove(curPiece, curX - 1, curY); break; case KeyEvent.VK_RIGHT: tryMove(curPiece, curX + 1, curY); break; case KeyEvent.VK_DOWN: tryMove(curPiece.rotateRight(), curX, curY); break; case KeyEvent.VK_UP: tryMove(curPiece.rotateLeft(), curX, curY); break; case KeyEvent.VK_SPACE: dropDown(); break; case KeyEvent.VK_D: oneLineDown(); break; } } } } ``` 游戲邏輯位于`Board`中。 ```java private final int BOARD_WIDTH = 10; private final int BOARD_HEIGHT = 22; ``` `BOARD_WIDTH`和`BOARD_HEIGHT`常數定義電路板的大小。 ```java ... private boolean isFallingFinished = false; private boolean isStarted = false; private boolean isPaused = false; private int numLinesRemoved = 0; private int curX = 0; private int curY = 0; ... ``` 我們初始化一些重要的變量。 `isFallingFinished`變量確定俄羅斯方塊形狀是否已完成下降,然后我們需要創建一個新形狀。 `numLinesRemoved`計算行數,到目前為止我們已經刪除了行數。 `curX`和`curY`變量確定下降的俄羅斯方塊形狀的實際位置。 ```java setFocusable(true); ``` 我們必須顯式調用`setFocusable()`方法。 從現在開始,開發板具有鍵盤輸入。 ```java int DELAY = 400; ``` `DELAY`常數定義游戲的速度。 ```java timer = new Timer(DELAY, this); timer.start(); ``` 在指定的延遲后,`Timer`對象將觸發一個或多個操作事件。 在我們的情況下,計時器每`DELAY` ms 調用一次`actionPerformed()`方法。 ```java @Override public void actionPerformed(ActionEvent e) { if (isFallingFinished) { isFallingFinished = false; newPiece(); } else { oneLineDown(); } } ``` `actionPerformed()`方法檢查下降是否已完成。 如果是這樣,將創建一個新作品。 如果不是,下降的俄羅斯方塊下降了一行。 在`doDrawing()`方法內部,我們在板上繪制了所有對象。 這幅畫有兩個步驟。 ```java for (int i = 0; i < BOARD_HEIGHT; ++i) { for (int j = 0; j < BOARD_WIDTH; ++j) { Tetrominoe shape = shapeAt(j, BOARD_HEIGHT - i - 1); if (shape != Tetrominoe.NoShape) drawSquare(g, j * squareWidth(), boardTop + i * squareHeight(), shape); } } ``` 在第一步中,我們繪制掉落到板底部的所有形狀或形狀的其余部分。 所有正方形都記在板數組中。 我們使用`shapeAt()`方法訪問它。 ```java if (curPiece.getShape() != Tetrominoe.NoShape) { for (int i = 0; i < 4; ++i) { int x = curX + curPiece.x(i); int y = curY - curPiece.y(i); drawSquare(g, 0 + x * squareWidth(), boardTop + (BOARD_HEIGHT - y - 1) * squareHeight(), curPiece.getShape()); } } ``` 在第二步中,我們繪制實際的下降部分。 ```java private void dropDown() { int newY = curY; while (newY > 0) { if (!tryMove(curPiece, curX, newY - 1)) { break; } --newY; } pieceDropped(); } ``` 如果我們按 `Space` 鍵,則該片段將落到底部。 我們只是簡單地嘗試將一塊下降到另一條俄羅斯方塊下降的底部或頂部。 當俄羅斯方塊結束下降時,將調用`pieceDropped()`。 ```java private void clearBoard() { for (int i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; ++i) { board[i] = Tetrominoe.NoShape; } } ``` `clearBoard()`方法用空的`NoShapes`填充電路板。 ```java private void pieceDropped() { for (int i = 0; i < 4; ++i) { int x = curX + curPiece.x(i); int y = curY - curPiece.y(i); board[(y * BOARD_WIDTH) + x] = curPiece.getShape(); } removeFullLines(); if (!isFallingFinished) { newPiece(); } } ``` `pieceDropped()`方法將下降的片段放入`board`數組。 棋盤再次保持了所有碎片的正方形和已經落下的碎片的剩余部分。 當一塊完成落下時,就該檢查是否可以去除板上的一些線了。 這是`removeFullLines()`方法的工作。 然后,我們嘗試創建一個新作品。 ```java private void newPiece() { curPiece.setRandomShape(); curX = BOARD_WIDTH / 2 + 1; curY = BOARD_HEIGHT - 1 + curPiece.minY(); if (!tryMove(curPiece, curX, curY)) { curPiece.setShape(Tetrominoe.NoShape); timer.stop(); isStarted = false; statusbar.setText("game over"); } } ``` `newPiece()`方法創建一個新的俄羅斯方塊。 作品獲得了新的隨機形狀。 然后,我們計算初始`curX`和`curY`值。 如果我們不能移動到初始位置,則游戲結束。 我們加油。 計時器停止。 我們將游戲放在狀態欄上的字符串上方。 ```java private boolean tryMove(Shape newPiece, int newX, int newY) { for (int i = 0; i < 4; ++i) { int x = newX + newPiece.x(i); int y = newY - newPiece.y(i); if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) { return false; } if (shapeAt(x, y) != Tetrominoe.NoShape) { return false; } } curPiece = newPiece; curX = newX; curY = newY; repaint(); return true; } ``` `tryMove()`方法嘗試移動俄羅斯方塊。 如果該方法已達到板邊界或與已經跌落的俄羅斯方塊相鄰,則返回`false`。 ```java int numFullLines = 0; for (int i = BOARD_HEIGHT - 1; i >= 0; --i) { boolean lineIsFull = true; for (int j = 0; j < BOARD_WIDTH; ++j) { if (shapeAt(j, i) == Tetrominoe.NoShape) { lineIsFull = false; break; } } if (lineIsFull) { ++numFullLines; for (int k = i; k < BOARD_HEIGHT - 1; ++k) { for (int j = 0; j < BOARD_WIDTH; ++j) board[(k * BOARD_WIDTH) + j] = shapeAt(j, k + 1); } } } ``` 在`removeFullLines()`方法內部,我們檢查`board`的所有行中是否有完整的行。 如果至少有一條實線,則將其刪除。 找到整條線后,我們增加計數器。 我們將整行上方的所有行向下移動一行。 這樣我們就破壞了整個生產線。 注意,在我們的俄羅斯方塊游戲中,我們使用了所謂的樸素重力。 這意味著正方形可能會漂浮在空白間隙上方。 每個俄羅斯方塊都有四個正方形。 每個正方形都使用`drawSquare()`方法繪制。 俄羅斯方塊有不同的顏色。 ```java g.setColor(color.brighter()); g.drawLine(x, y + squareHeight() - 1, x, y); g.drawLine(x, y, x + squareWidth() - 1, y); ``` 正方形的左側和頂部以較亮的顏色繪制。 類似地,底部和右側用較深的顏色繪制。 這是為了模擬 3D 邊緣。 我們使用鍵盤控制游戲。 控制機制通過`KeyAdapter`實現。 這是一個覆蓋`keyPressed()`方法的內部類。 ```java case KeyEvent.VK_LEFT: tryMove(curPiece, curX - 1, curY); break; ``` 如果按向左箭頭鍵,則嘗試將下降片向左移動一個正方形。 ![Tetris](https://img.kancloud.cn/6e/9d/6e9d5cf19a148af766b6dae3e2bd8109_200x400.jpg) 圖:俄羅斯方塊 這是俄羅斯方塊游戲。 {% endraw %}
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看