<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 俄羅斯方塊 > 原文: [https://zetcode.com/gfx/java2d/tetris/](https://zetcode.com/gfx/java2d/tetris/) 在本章中,我們將在 Java Swing 中創建一個俄羅斯方塊游戲克隆。 ## 俄羅斯方塊 俄羅斯方塊游戲是有史以來最受歡迎的計算機游戲之一。 原始游戲是由俄羅斯程序員 Alexey Pajitnov 于 1985 年設計和編程的。此后,幾乎所有版本的幾乎所有計算機平臺上都可以使用俄羅斯方塊。 甚至我的手機都有俄羅斯方塊游戲的修改版。 俄羅斯方塊被稱為下降塊益智游戲。 在這個游戲中,我們有七個不同的形狀,稱為 tetrominoes 。 S 形,Z 形,T 形,L 形,線形,鏡像 L 形和正方形。 這些形狀中的每一個都形成有四個正方形。 形狀從板上掉下來。 俄羅斯方塊游戲的目的是移動和旋轉形狀,以便它們盡可能地適合。 如果我們設法形成一行,則該行將被破壞并得分。 我們玩俄羅斯方塊游戲,直到達到頂峰。 ![Tetrominoes](https://img.kancloud.cn/2b/7a/2b7a874cd2ec9a34c259d3dd686809e9_328x132.jpg) 圖:Tetrominoes ## 開發 我們的俄羅斯方塊游戲沒有圖像,我們使用 Swing 繪圖 API 繪制四面體。 每個計算機游戲的背后都有一個數學模型。 俄羅斯方塊也是如此。 游戲背后的一些想法。 * 我們使用計時器類創建游戲周期 * 繪制四方塊 * 形狀以正方形為單位移動(不是逐個像素移動) * 從數學上講,棋盤是簡單的數字列表 我對游戲做了一些簡化,以便于理解。 游戲啟動后立即開始。 我們可以通過按`p`鍵暫停游戲。 空格鍵將把俄羅斯方塊放在底部。 `d`鍵將片段向下一行。 (它可以用來加快下降速度。)游戲以恒定速度運行,沒有實現加速。 分數是我們已刪除的行數。 `Tetris.java` ```java package com.zetcode; import java.awt.BorderLayout; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class Tetris extends JFrame { private JLabel statusbar; public Tetris() { initUI(); } private void initUI() { statusbar = new JLabel(" 0"); add(statusbar, BorderLayout.SOUTH); Board board = new Board(this); add(board); board.start(); setSize(200, 400); setTitle("Tetris"); setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); } public JLabel getStatusBar() { return statusbar; } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Tetris game = new Tetris(); game.setVisible(true); } }); } } ``` 在`Tetris.java`文件中,我們設置了游戲。 我們創建一個玩游戲的棋盤。 我們創建一個狀態欄。 ```java board.start(); ``` `start()`方法啟動俄羅斯方塊游戲。 窗口出現在屏幕上之后。 `Shape.java` ```java package com.zetcode; import java.util.Random; public class Shape { protected enum Tetrominoes { NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape }; private Tetrominoes pieceShape; private int coords[][]; private int[][][] coordsTable; public Shape() { coords = new int[4][2]; setShape(Tetrominoes.NoShape); } public void setShape(Tetrominoes 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++) { for (int j = 0; j < 2; ++j) { coords[i][j] = coordsTable[shape.ordinal()][i][j]; } } pieceShape = shape; } private void setX(int index, int x) { coords[index][0] = x; } private void setY(int index, int y) { coords[index][1] = y; } public int x(int index) { return coords[index][0]; } public int y(int index) { return coords[index][1]; } public Tetrominoes getShape() { return pieceShape; } public void setRandomShape() { Random r = new Random(); int x = Math.abs(r.nextInt()) % 7 + 1; Tetrominoes[] values = Tetrominoes.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; } public int minY() { int m = coords[0][1]; for (int i=0; i < 4; i++) { m = Math.min(m, coords[i][1]); } return m; } public Shape rotateLeft() { if (pieceShape == Tetrominoes.SquareShape) return this; Shape 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; } public Shape rotateRight() { if (pieceShape == Tetrominoes.SquareShape) return this; Shape 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 Tetrominoes { NoShape, ZShape, SShape, LineShape, TShape, SquareShape, LShape, MirroredLShape }; ``` `Tetrominoes`枚舉擁有所有七個俄羅斯方塊形狀。 加上此處稱為`NoShape`的空形狀。 ```java public Shape() { coords = new int[4][2]; setShape(Tetrominoes.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++) { for (int j = 0; j < 2; ++j) { coords[i][j] = coordsTable[shape.ordinal()][i][j]; } } ``` 在這里,我們將一行坐標值從`coordsTable`放置到俄羅斯方塊的`coords`數組中。 請注意`ordinal()`方法的使用。 在 C++ 中,枚舉類型本質上是一個整數。 與 C++ 不同,Java 枚舉是完整類。 并且`ordinal()`方法返回枚舉類型在枚舉對象中的當前位置。 下圖將幫助您更多地了解坐標值。 `coords`數組保存俄羅斯方塊的坐標。 例如,數字`{0, -1}`,`{0, 0}`,`{-1, 0}`,`{-1, -1}`表示旋轉的 S 形。 下圖說明了形狀。 ![Coordinates](https://img.kancloud.cn/5c/0b/5c0bbb2ed04c22b46915ea1ef803ce4d_272x230.jpg) 圖:坐標 ```java public Shape rotateLeft() { if (pieceShape == Tetrominoes.SquareShape) return this; Shape 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; } ``` 此代碼將棋子向左旋轉。 正方形不必旋轉。 這就是為什么我們只是將引用返回到當前對象。 查看上一張圖像將有助于理解旋轉。 `Board.java` ```java package com.zetcode; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.Timer; import com.zetcode.Shape.Tetrominoes; public class Board extends JPanel implements ActionListener { private final int BoardWidth = 10; private final int BoardHeight = 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 Tetrominoes[] board; public Board(Tetris parent) { initBoard(parent); } private void initBoard(Tetris parent) { setFocusable(true); curPiece = new Shape(); timer = new Timer(400, this); timer.start(); statusbar = parent.getStatusBar(); board = new Tetrominoes[BoardWidth * BoardHeight]; addKeyListener(new TAdapter()); clearBoard(); } @Override public void actionPerformed(ActionEvent e) { if (isFallingFinished) { isFallingFinished = false; newPiece(); } else { oneLineDown(); } } private int squareWidth() { return (int) getSize().getWidth() / BoardWidth; } private int squareHeight() { return (int) getSize().getHeight() / BoardHeight; } private Tetrominoes shapeAt(int x, int y) { return board[(y * BoardWidth) + x]; } public 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) { Dimension size = getSize(); int boardTop = (int) size.getHeight() - BoardHeight * squareHeight(); for (int i = 0; i < BoardHeight; ++i) { for (int j = 0; j < BoardWidth; ++j) { Tetrominoes shape = shapeAt(j, BoardHeight - i - 1); if (shape != Tetrominoes.NoShape) drawSquare(g, 0 + j * squareWidth(), boardTop + i * squareHeight(), shape); } } if (curPiece.getShape() != Tetrominoes.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 + (BoardHeight - 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 < BoardHeight * BoardWidth; ++i) board[i] = Tetrominoes.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 * BoardWidth) + x] = curPiece.getShape(); } removeFullLines(); if (!isFallingFinished) newPiece(); } private void newPiece() { curPiece.setRandomShape(); curX = BoardWidth / 2 + 1; curY = BoardHeight - 1 + curPiece.minY(); if (!tryMove(curPiece, curX, curY)) { curPiece.setShape(Tetrominoes.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 >= BoardWidth || y < 0 || y >= BoardHeight) return false; if (shapeAt(x, y) != Tetrominoes.NoShape) return false; } curPiece = newPiece; curX = newX; curY = newY; repaint(); return true; } private void removeFullLines() { int numFullLines = 0; for (int i = BoardHeight - 1; i >= 0; --i) { boolean lineIsFull = true; for (int j = 0; j < BoardWidth; ++j) { if (shapeAt(j, i) == Tetrominoes.NoShape) { lineIsFull = false; break; } } if (lineIsFull) { ++numFullLines; for (int k = i; k < BoardHeight - 1; ++k) { for (int j = 0; j < BoardWidth; ++j) board[(k * BoardWidth) + j] = shapeAt(j, k + 1); } } } if (numFullLines > 0) { numLinesRemoved += numFullLines; statusbar.setText(String.valueOf(numLinesRemoved)); isFallingFinished = true; curPiece.setShape(Tetrominoes.NoShape); repaint(); } } private void drawSquare(Graphics g, int x, int y, Tetrominoes 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) }; Color 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() == Tetrominoes.NoShape) { return; } int keycode = e.getKeyCode(); if (keycode == 'p' || 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 'd': oneLineDown(); break; case 'D': oneLineDown(); break; } } } } ``` 最后,我們有`Board.java`文件。 這是游戲邏輯所在的位置。 ```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 timer = new Timer(400, this); timer.start(); ``` `Timer`對象在指定的延遲后觸發一個或多個操作事件。 在我們的例子中,計時器每 400ms 調用一次`actionPerformed()`方法。 ```java @Override public void actionPerformed(ActionEvent e) { if (isFallingFinished) { isFallingFinished = false; newPiece(); } else { oneLineDown(); } } ``` `actionPerformed()`方法檢查下降是否已完成。 如果是這樣,將創建一個新作品。 如果不是,下降的俄羅斯方塊下降了一行。 在`doDrawing()`方法內部,我們在板上繪制了所有對象。 這幅畫有兩個步驟。 ```java for (int i = 0; i < BoardHeight; ++i) { for (int j = 0; j < BoardWidth; ++j) { Tetrominoes shape = shapeAt(j, BoardHeight - i - 1); if (shape != Tetrominoes.NoShape) drawSquare(g, 0 + j * squareWidth(), boardTop + i * squareHeight(), shape); } } ``` 在第一步中,我們繪制所有形狀或已掉落到板底部的形狀的其余部分。 所有正方形都記在板數組中。 我們使用`shapeAt()`方法訪問它。 ```java if (curPiece.getShape() != Tetrominoes.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 + (BoardHeight - y - 1) * squareHeight(), curPiece.getShape()); } } ``` 在第二步中,我們繪制實際的下降部分。 ```java private void dropDown() { int newY = curY; while (newY > 0) { if (!tryMove(curPiece, curX, newY - 1)) break; --newY; } pieceDropped(); } ``` 如果按空格鍵,則該片段將落到底部。 我們只是簡單地嘗試將一塊下降到另一條俄羅斯方塊下降的底部或頂部。 ```java private void clearBoard() { for (int i = 0; i < BoardHeight * BoardWidth; ++i) board[i] = Tetrominoes.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 * BoardWidth) + x] = curPiece.getShape(); } removeFullLines(); if (!isFallingFinished) newPiece(); } ``` `pieceDropped()`方法將下降的碎片放入板數組中。 棋盤再次保持了所有碎片的正方形和已經落下的碎片的剩余部分。 當一塊完成落下時,就該檢查是否可以去除板上的一些線了。 這是`removeFullLines()`方法的工作。 然后,我們創建一個新作品。 更準確地說,我們嘗試創建一個新作品。 ```java private void newPiece() { curPiece.setRandomShape(); curX = BoardWidth / 2 + 1; curY = BoardHeight - 1 + curPiece.minY(); if (!tryMove(curPiece, curX, curY)) { curPiece.setShape(Tetrominoes.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 >= BoardWidth || y < 0 || y >= BoardHeight) return false; if (shapeAt(x, y) != Tetrominoes.NoShape) return false; } curPiece = newPiece; curX = newX; curY = newY; repaint(); return true; } ``` `tryMove()`方法嘗試移動俄羅斯方塊。 如果該方法已達到板邊界或與已經跌落的俄羅斯方塊相鄰,則返回`false`。 ```java int numFullLines = 0; for (int i = BoardHeight - 1; i >= 0; --i) { boolean lineIsFull = true; for (int j = 0; j < BoardWidth; ++j) { if (shapeAt(j, i) == Tetrominoes.NoShape) { lineIsFull = false; break; } } if (lineIsFull) { ++numFullLines; for (int k = i; k < BoardHeight - 1; ++k) { for (int j = 0; j < BoardWidth; ++j) board[(k * BoardWidth) + j] = shapeAt(j, k + 1); } } } ``` 在`removeFullLines()`方法內部,我們檢查板中所有行中是否有完整行。 如果至少有一條實線,則將其刪除。 找到整條線后,我們增加計數器。 我們將整行上方的所有行向下移動一行。 這樣我們就破壞了整個生產線。 注意,在我們的俄羅斯方塊游戲中,我們使用了所謂的天真重力。 這意味著,正方形可能會漂浮在空白間隙上方。 每個俄羅斯方塊都有四個正方形。 每個正方形都使用`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) 圖:俄羅斯方塊 這是俄羅斯方塊游戲。
                  <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>

                              哎呀哎呀视频在线观看