俄羅斯方塊的小實現
阿新 • • 發佈:2019-01-03
最近在老師帶領下做了一個俄羅斯方塊的小遊戲~最後執行介面是這樣的:小方格和背景的圖片都是現成的,大致思路是:代表每個小方格移動的Cell類->組成好的塊(四格小方格組成的大塊)的類Tetromino->表示每個具體圖形的位置和顏色的類(一共七個)->進行遊戲具體操作的類TetrisCell類比較簡單,屬性有行、列和屬性,提供相應的get、set函式,以及每個小塊的向左、向右、向下移動。Tetromino類也同樣,整合了Cell類,其中的方法實現四格方塊整體的向左、向右、向下移動,以及隨機生成一個正在下落的塊和將要下落的塊。Tetris類就比較複雜啦..首先是初始介面。介面需要繼承JPanel類,重寫paint方法實現在面板上自動繪製圖形,JFrame與JPanel的區別我在網上查詢到的是:Jpanel不是頂級視窗,不能直接輸出。它必須放在像JFrame這樣的頂級視窗上才能輸出 ,JFrame只是一個介面,也就是個框架,要想把控制元件放在該介面中,必須把控制元件放在JPanel中,然後再把JPanel放在JFrame中,JPanel作為一個容器使用。表示小方塊和背景的圖片需要放到和程式一個包下,設定為靜態,每次初次載入程式即可載入完成,沒有卡頓的可能性。
需要呼叫在Tetromino類中寫好的隨機獲取某個方塊的函式(隨機0-6,每個數字代表一個圖形),獲取後確定了圖形的形狀和初始位置,繪製出來即可。接下來是圖形的下落過程。 重寫paint方法,在方法中繪製牆、正在下落和即將下落的方塊。paint方法將被自動呼叫,不需要顯式呼叫。到現在玩遊戲前的頁面就做好了。
public static BufferedImage T; //T形狀的方塊 以下以此類推
public static BufferedImage I;
新增圖片的過程也是靜態的,載入一次即可。然後是繪製格子。static { try { T=ImageIO.read(Tetris.class.getResource("T.png")); //其他以此類推 background=ImageIO.read(Tetris.class.getResource("tetris.png")); }catch(Exception e) { e.printStackTrace(); } }
if(cell==null) {
g.drawRect(x, y, CELL_SIZE, CELL_SIZE);} //x表示橫座標
else {
g.drawImage(cell.getImage(),x,y,null);}
通過兩層for迴圈實現繪製格子,在內層迴圈中如果wall[i][j]位置為空,則繪製一個空的格子,如果不空,則繪製該位置應有的格子圖片,即為了在每個塊落到底部時能被繪製出來。然後是繪製正在和即將下落的方塊。需要呼叫在Tetromino類中寫好的隨機獲取某個方塊的函式(隨機0-6,每個數字代表一個圖形),獲取後確定了圖形的形狀和初始位置,繪製出來即可。接下來是圖形的下落過程。
Tetris類的屬性有正在下落、即將下落的四格方塊組成的圖形(有七種)和牆(方塊在牆上移動,牆為20*10的方格)。
private Tetromino currentOne=Tetromino.randomOne();
private Tetromino nextOne=Tetromino.randomOne();
private Cell[][] wall=new Cell[20][10]; //設定牆為20*10的方格組合
private static final int CELL_SIZE=26; //寬度為26接下來是玩遊戲的方法,也是遊戲的主要邏輯。定義start方法,在其中建立一個鍵盤監聽器物件,接受鍵盤的下左右事件,並重新繪製新位置的圖形(repaint方法即可實現,repaint()通過呼叫執行緒再由執行緒去呼叫update()方法清除當前顯示並再呼叫paint()方法進行繪製下一個需要顯示的內容.這樣就起到了一種圖片的交替顯示從而在視角上形成了動畫,update()即用來清除當前顯示並呼叫paint()方法)。為了使程式的執行速度看起來慢一些(即為了防止塊下落的過快而人來不及反應),設定了程式的休眠時間為300毫秒。也需要判斷是否能繼續下落,即下一行的格有沒有方塊或者有沒有到達底部,如果沒有則繼續下落,如果有則停止下落,在牆上的相應位置設定有方塊(即非null),並更新下一個圖形。再呼叫repaint函式,使鍵盤沒有任何操作時,圖形仍能繼續下落。在鍵盤有操作時,需要判斷是按了哪個鍵,這件事鍵盤監聽的物件即可做到。不同的鍵選擇不同的操作。在判斷沒有左右出界和即將移動的地方沒有其他方塊後,呼叫Tetromino中的移動函式,移動整個圖形,如果不再能改變位置,則在牆上表明位置然後繪製這個圖形。(在此處需要在鍵盤產生時間後立刻改變牆的位置,在下次畫圖時能直接畫到移動到的位置,如果按很多次下而不實時改變牆中的位置,則會影響遊戲體驗,方塊並沒有因為按動的頻率加快而速度加快,不符合遊戲人的需要)。在移動時,先向該移動的方向移動,如果出界或重合,則再向反方向移動一次。判斷左右出界時,如果列小於0或者大於9則出界,判斷即將移動的位置有無方塊判斷牆的對應位置是否為空即可。判斷這個時不能先判斷有無重合,因為在這種操作中可能存在列號為-1時,判斷重合函式會報錯,如果把判斷出界放在前面,即會捕捉這個錯誤,或語句也不會繼續執行。上述即是目前簡單實現方塊移動的過程,之後還會實現圖形的旋轉~附全部程式碼:Cell類:import java.awt.image.BufferedImage; /** * 俄羅斯方塊中的最小單位-方格 * 屬性:row col image * 行為(方法):left() right() down() */ public class Cell { private int row;//行 private int col;//列 private BufferedImage image; public Cell(int row, int col, BufferedImage image) { super(); this.row = row; this.col = col; this.image = image; } public Cell() { super(); } public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getCol() { return col; } public void setCol(int col) { this.col = col; } public BufferedImage getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } @Override public String toString() { return "Cell [row=" + row + ", col=" + col + ", image=" + image + "]"; } /* 向左移動*/ public void left() { col--; } /* 向右移動*/ public void right() { col++; } /* 向下移動*/ public void drop() { row++; } }Tetromino類:
/**
* 四格方塊
* 屬性:
* --cells ---四個方塊
* 行為;
* moveLeft()
* moveRight()
* softDrop()
*
*/
public class Tetromino {
protected Cell[] cells=new Cell[4];
public void moveLeft()
{
for(Cell c:cells)
c.left();
}
public void moveRight()
{
for(int i=0;i<4;i++)
cells[i].right();
}
public void softDrop()
{
for(int i=0;i<4;i++)
cells[i].drop();
}
/*
* 隨機生成一個四格方塊
*/
public static Tetromino randomOne() {
Tetromino t=null;
int num=(int)(Math.random()*7);
switch(num)
{
case 0: t=new T();break;
case 1: t=new O();break;
case 2: t=new I();break;
case 3: t=new J();break;
case 4: t=new L();break;
case 5: t=new S();break;
case 6: t=new Z();break;
}
return t;
}
}
Tetris類:import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
/*
* 俄羅斯方塊的主類
* 載入靜態資源
* 前提:必須是面板JPanel,可以嵌入視窗
* 面板上自帶一個畫筆,有一個功能:自動繪製
* 其實是呼叫了JPanel裡的paint()
*/
public class Tetris extends JPanel{
/*
* 屬性:正在下落的四格方塊
* 將要下落的四格方塊
*/
private Tetromino currentOne=Tetromino.randomOne();
private Tetromino nextOne=Tetromino.randomOne();
/*
* 屬性:牆 20*10的方格 寬度為26
*/
private Cell[][] wall=new Cell[20][10];
private static final int CELL_SIZE=26;
public static BufferedImage T; //T形狀的方塊 以下以此類推
public static BufferedImage I;
public static BufferedImage O;
public static BufferedImage J;
public static BufferedImage L;
public static BufferedImage S;
public static BufferedImage Z;
public static BufferedImage background; //背景圖片
static {
try {
T=ImageIO.read(Tetris.class.getResource("T.png"));
I=ImageIO.read(Tetris.class.getResource("I.png"));
O=ImageIO.read(Tetris.class.getResource("O.png"));
J=ImageIO.read(Tetris.class.getResource("J.png"));
L=ImageIO.read(Tetris.class.getResource("L.png"));
S=ImageIO.read(Tetris.class.getResource("S.png"));
Z=ImageIO.read(Tetris.class.getResource("Z.png"));
background=ImageIO.read(Tetris.class.getResource("tetris.png"));
}catch(Exception e)
{
e.printStackTrace();
}
}
public void paintWall(Graphics g) {
//外層迴圈控制行數
for(int i=0;i<20;i++)
{
//內層迴圈控制列數
for(int j=0;j<10;j++)
{
int x=j*CELL_SIZE;
int y=i*CELL_SIZE;
Cell cell=wall[i][j];
if(cell==null) {
g.drawRect(x, y, CELL_SIZE, CELL_SIZE);
}
else {
g.drawImage(cell.getImage(),x,y,null);
}
}
}
}
public void paintCurrentOne(Graphics g)
{
Cell[] cells=currentOne.cells;
for(int i=0;i<cells.length;i++)
{
int x=cells[i].getCol()*CELL_SIZE;
int y=cells[i].getRow()*CELL_SIZE;
g.drawImage(cells[i].getImage(), x, y, null);
}
}
//繪製即將下落的另一個
public void paintNextOne(Graphics g)
{
Cell[] cells=nextOne.cells;
for(Cell cell:cells)
{
int col=cell.getCol();
int row=cell.getRow();
int x=col*CELL_SIZE+260;
int y=row*CELL_SIZE+26;
g.drawImage(cell.getImage(), x, y, null);
}
}
//重寫JPanel中的paint方法
public void paint(Graphics g)
{
/*
* 背景 g:畫筆 g.drawImage(image,x,y,null)
* image要繪製的圖片
* x y 為開始繪製的橫縱座標
*
*/
g.drawImage(background, 0, 0,null);
//平移座標軸
g.translate(15, 15);
//繪製牆
paintWall(g);
//繪製正在下落的四格方塊
paintCurrentOne(g);
//繪製即將下落的四格方塊
paintNextOne(g);
}
/*
*封裝了遊戲的主要邏輯
*/
public void start()
{
//開啟鍵盤監聽事件
KeyListener listener=new KeyAdapter() {
/*
* keyPressed()是鍵盤按鈕按下去所呼叫的方法
*/
@Override
public void keyPressed(KeyEvent e) {
//獲取一個毽子的代號
int code=e.getKeyCode();
switch(code) {
case KeyEvent.VK_DOWN:
softDropAction();
break;
case KeyEvent.VK_LEFT:
moveLeftAction();
break;
case KeyEvent.VK_RIGHT:
moveRightAction();
break;
}
repaint();
}
};
//面板新增事件監聽物件listener
this.addKeyListener(listener);
//面板物件設定成焦點
this.requestFocus();
while(true)
{
/*
* 當程式執行到此,會進入休眠狀態
* 睡眠時間為200毫秒,單位為毫秒
* 300毫秒之後,會自動執行後續程式碼
*/
try {
Thread.sleep(300);
}catch(InterruptedException e)
{
e.printStackTrace();
}
if(canDrop())
{
currentOne.softDrop();
}
else {
LandToWall();
currentOne=nextOne;
nextOne=Tetromino.randomOne();
}
/*
* 下落之後,需要重新繪製,才會看到下落後的位置
* repaint方法發也是JPanel類中 提供的
* 此方法呼叫paint方法
*/
repaint();
}
}
/*
* 使用down控制四格方塊的下落
*/
public void softDropAction() {
if(canDrop())
{
currentOne.softDrop();
}
else {
LandToWall();
currentOne=nextOne;
nextOne=Tetromino.randomOne();
}
}
/*
* 使用left鍵控制向左的行為
*/
public void moveLeftAction() {
//沒出界或者沒和左面的方塊重合
currentOne.moveLeft();;
if(outOfBounds()||coincide())
{
currentOne.moveRight();;
}
}
private boolean coincide() {
Cell[] cells=currentOne.cells;
for(Cell cell:cells) {
int row=cell.getRow();
int col=cell.getCol();
if(wall[row][col]!=null)
{
return true;
}
}
return false;
}
private boolean outOfBounds() {
Cell[] cells=currentOne.cells;
for(Cell cell:cells) {
int col=cell.getCol();
if(col<0||col>9)
return true;
}
return false;
}
public void moveRightAction() {
//沒出界或者右面的方塊重合
currentOne.moveRight();
//不可以改變這兩個函式的位置 因為coincide裡的陣列不允許引數為-1的時候
if(outOfBounds()||coincide())
{
currentOne.moveLeft();
}
}
/*
* 判斷是否下落
*/
public boolean canDrop()
{
Cell[] cells=currentOne.cells;
for(Cell cell:cells) {
/*
* 獲取每個元素的行號和列號
* 判斷
* 只要有一個元素的下一行有方塊
* 或者只要有一個元素到達最後一行
* 就不能再下落了
*/
int row=cell.getRow();
int col=cell.getCol();
if(row==19) {
return false;
}
if(wall[row+1][col]!=null) {
return false;
}
}
return true;
}
/*
* 當不能再下落時,需要將四格方塊,嵌入到牆中
* 也就是儲存在二維陣列中相應位置中
*/
public void LandToWall() {
Cell[] cells=currentOne.cells;
for(Cell cell:cells) {
int row=cell.getRow();
int col=cell.getCol();
wall[row][col]=cell;
}
}
//啟動遊戲的入口
public static void main(String[] args) {
//1、建立一個視窗物件
JFrame frame=new JFrame("俄羅斯方塊");
//建立遊戲介面即面板
Tetris panel=new Tetris();
//將面板嵌入視窗
frame.add(panel);
//2、設定為可見
frame.setVisible(true);
//3、設定視窗大小尺寸
frame.setSize(535, 600);
//4、設定視窗居中
frame.setLocationRelativeTo(null);
//5、設定視窗關閉,即程式終止
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setBackground(Color.yellow);
panel.start();
}
}
各個圖形對應的位置: