1. 程式人生 > >Java簡易俄羅斯方塊

Java簡易俄羅斯方塊

目前學到Swing第二章,這兩天沒有學新知識,寫了個俄羅斯方塊。 
寫之前想不好怎麼實現,找來別人的程式看了一下,再加上自己的想法,做了下面這個半成品,接下來可以加上各種選單、按鈕貼圖等美化,都是些錦上添花的動作,繁瑣但不難。 
我覺得寫俄羅斯方塊,難點在於如何將方塊的形狀(七種)、狀態(四種翻轉)、動作(左、右、下落)等提煉成陣列。 這裡採用四維陣列來表示每一種方塊:【種類】【翻轉】【X座標】【Y座標】,每種方塊都畫在一個4*4的大方塊中,把這個大方塊作為一個整體進行移動。再寫了一個方法遍歷大方塊,判斷該方塊的位置是否合法。 
有了合適的模型,再把每個步驟轉化成程式碼,就不難了。


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JCheckBox;
import javax.swing.Timer;
import javax.swing.JOptionPane;


public class KingTetris{
    // 20行,10列
    private final int ROW = 20;
    private final int COL = 10;

    // 每個方塊邊長,單位為畫素
    private final int LEN = 35;

    // 遊戲區域的左、上邊距
    private final int LEFT_MARGIN = LEN*2;
    private final int UP_MARGIN = LEN;    
    
    // 面布大小,單位為畫素
    private final int AREA_WIDTH = LEN*22;
    private final int AREA_HEIGHT = LEN*22;

    // 是否需要網格
    private boolean showGrid = true;

    // 是否彩色,將來可以作為貼圖控制
    private boolean isColor = true;

    // 得分
    private int score = 0;

    // 畫布
    private MyCanvas drawArea = new MyCanvas();
    // 視窗
    private JFrame f = new JFrame("俄羅斯方塊");
    
    // 畫圖用的image
    private BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB);
    private Graphics g = image.createGraphics();

    // 陣列,用於儲存背景
    private int[][] map = new int[COL][ROW];
    // 陣列,用於儲存顏色
    private Color[] color = new Color[]{Color.green, Color.red, Color.orange, Color.blue, Color.cyan, Color.yellow, Color.magenta, Color.gray};
    //預設灰色
    private final int DEFAULT = 7;
    private Color[][] mapColor = new Color[COL][ROW];

    //元件的橫座標
    int wordX = LEN*14;// 元件的橫座標
    int wordY = LEN*9; // 字的初始縱座標

    //shape的四個引數
    private int type, state, x, y, nextType, nextState;

    //如果剛開始遊戲,由於無nextType,先給type等隨機一個,下為首次開始遊戲的標誌
    private boolean newBegin = true;

    // 用陣列來代表不同形狀的下墜物,四維分別是型別Type、旋轉狀態State、橫座標X、縱座標Y。畫示意圖即可得出座標
    // 方塊共有7種,分別以S、Z、L、J、I、O、T這7個字母的形狀來命名
    private int[][][][] shape = new int[][][][]{
        // S的四種翻轉狀態:
        { { {0,1,0,0}, {1,1,0,0}, {1,0,0,0}, {0,0,0,0} }, 
        { {0,0,0,0}, {1,1,0,0}, {0,1,1,0}, {0,0,0,0} }, 
        { {0,1,0,0}, {1,1,0,0}, {1,0,0,0}, {0,0,0,0} }, 
        { {0,0,0,0}, {1,1,0,0}, {0,1,1,0}, {0,0,0,0} } },
        // Z:
        { { {1,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0} }, 
        { {0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} }, 
        { {1,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0} }, 
        { {0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} } },
        // L:
        { { {0,0,0,0}, {1,1,1,0}, {0,0,1,0}, {0,0,0,0} }, 
        { {0,0,0,0}, {0,1,1,0}, {0,1,0,0}, {0,1,0,0} }, 
        { {0,0,0,0}, {0,1,0,0}, {0,1,1,1}, {0,0,0,0} }, 
        { {0,0,1,0}, {0,0,1,0}, {0,1,1,0}, {0,0,0,0} } },
        // J:
        { { {0,0,0,0}, {0,0,1,0}, {1,1,1,0}, {0,0,0,0} }, 
        { {0,0,0,0}, {0,1,1,0}, {0,0,1,0}, {0,0,1,0} }, 
        { {0,0,0,0}, {0,1,1,1}, {0,1,0,0}, {0,0,0,0} }, 
        { {0,1,0,0}, {0,1,0,0}, {0,1,1,0}, {0,0,0,0} } },
        // I:
        { { {0,1,0,0}, {0,1,0,0}, {0,1,0,0}, {0,1,0,0} }, 
        { {0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0} }, 
        { {0,1,0,0}, {0,1,0,0}, {0,1,0,0}, {0,1,0,0} }, 
        { {0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0} } },
        // O:
        { { {0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0} }, 
        { {0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}  }, 
        { {0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}  }, 
        { {0,0,0,0}, {0,1,1,0}, {0,1,1,0}, {0,0,0,0}  } },
        // T:
        { { {0,1,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0} }, 
        { {0,0,0,0}, {1,1,1,0}, {0,1,0,0}, {0,0,0,0} }, 
        { {0,1,0,0}, {0,1,1,0}, {0,1,0,0}, {0,0,0,0} }, 
        { {0,1,0,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0} } },

    };

    /**
     * 初始化介面
     */
    private void init(){
        drawArea.setPreferredSize(new Dimension(AREA_WIDTH, AREA_HEIGHT));
        f.add(drawArea);
        JCheckBox gridCB = new JCheckBox("顯示網格",true);
        JCheckBox colorCB = new JCheckBox("彩色方塊", false);
        gridCB.setBounds(wordX, wordY-LEN,LEN,LEN);
        colorCB.setBounds(wordX, wordY-2*LEN,LEN,LEN);

        // paintArea();
        // 加鍵盤監聽器
        f.addKeyListener(new KeyAdapter(){
            public void keyPressed(KeyEvent e){
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_UP:
                        turn();
                        break;
                    case KeyEvent.VK_LEFT:
                        left();
                        break;
                    case KeyEvent.VK_RIGHT:
                        right();
                        break;
                    case KeyEvent.VK_DOWN:
                        down();
                        break;
                }                
            }
        });   
        Timer timer = new Timer(1000, new timerListener());
        newShape();
        timer.start();
        // 視窗顯示在螢幕正中
        // Toolkit是抽象類,只能用getDefaultToolkit()方法來獲取例項。
        // getScreenSize()方法返回的是一個Dimension物件,還須用getWidth()獲取寬度
        f.pack();
        int screenSizeX = (int)Toolkit.getDefaultToolkit().getScreenSize().getWidth();
        int screenSizeY = (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight();
        int fSizeX = (int)f.getSize().getWidth();
        int fSizeY = (int)f.getSize().getHeight();
        f.setResizable(false);// 禁止改變Frame大小
        f.setBounds((screenSizeX-fSizeX)/2, (screenSizeY-fSizeY)/2, fSizeX,fSizeY );    
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.setVisible(true);
    }

    /**
     * 繪圖
     */
    private void paintArea(){
        //  預設黑色,填充白色
        g.setColor(Color.white);
        g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);
        //  方格線
        //  先畫最外圍
        g.setColor(Color.gray);
        for (int offset = 0; offset <= 2; offset++){
            g.drawRect(LEFT_MARGIN-offset, UP_MARGIN-offset, COL*LEN+offset*2, ROW*LEN+offset*2);
        }
        
        //  如果showGrid為true則顯示網格
        if(showGrid){
            g.setColor(Color.gray);
            // 9條豎線
            for (int i = 1 ; i <= 9; i++){
                g.drawLine(LEFT_MARGIN+LEN*i, UP_MARGIN, LEFT_MARGIN+LEN*i, UP_MARGIN+ROW*LEN);
            }
            // 19條橫線
            for(int i = 1; i <= 19; i++){
                g.drawLine(LEFT_MARGIN, UP_MARGIN+LEN*i, LEFT_MARGIN+COL*LEN, UP_MARGIN+LEN*i);
            }
        }
        // 右上角顯示下一個shape        
        int offset2 = 3;// 邊框粗細
        int col = 4;// 右上角方框的列數
        int row = 4;// 行數
        g.setColor(Color.gray);
        g.setFont(new Font("Microsoft YaHei Mono", Font.BOLD, 20));
        g.drawString("下一個:", wordX, LEN*2);
        int nextX = wordX;
        int nextY = LEN*2;
        //暫不畫方框
        // for (int offset = 0; offset <= 2; offset++){
        //     g.drawRect(nextX-offset+10, nextY+10-offset, col*LEN+offset*2, row*LEN+offset*2);
        // }
        //畫下一次出現的下墜方塊
        g.setColor(isColor?color[nextType]:color[DEFAULT]);
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4; j++){
                if (shape[nextType][nextState][i][j]==1) {
                    g.fill3DRect(nextX+10+i*LEN, nextY+10+j*LEN, LEN, LEN,true);               
                }
            }
        }
        g.setColor(Color.gray);
        g.setFont(new Font("Times", Font.BOLD, 15));      
        g.drawString("玩法:", wordX, wordY+LEN*2);
        g.drawString("上箭頭:翻轉", wordX, wordY+LEN*3);
        g.drawString("左箭頭:左移", wordX, wordY+LEN*4);
        g.drawString("右箭頭:右移", wordX, wordY+LEN*5);
        g.drawString("下箭頭:下落", wordX, wordY+LEN*6);
        g.setFont(new Font("Times", Font.BOLD, 25));
        g.drawString("得分:" + score, wordX, wordY+LEN*8);
        //畫下墜物shape
        g.setColor(isColor?color[type]:color[DEFAULT]);
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4; j++){
                if (shape[type][state][i][j]==1) {
                    g.fill3DRect(LEFT_MARGIN+(x+i)*LEN, UP_MARGIN+(y+j)*LEN, LEN, LEN,true);               
                }
            }
        }
        //畫背景map
        for(int i = 0; i < COL; i++){
            for(int j = 0; j < ROW; j++){
                if (map[i][j] == 1) {
                    g.setColor(mapColor[i][j]);
                    g.fill3DRect(LEFT_MARGIN+i*LEN, UP_MARGIN+j*LEN, LEN, LEN,true);
                }
            }
        }

        drawArea.repaint();
    }

    /**
     * 自定義畫布,重寫paint()方法
     */
    private class MyCanvas extends JPanel{
        public void paint(Graphics g){
            g.drawImage(image, 0, 0, null);
        }
    }

    /**
     * 判斷位置是否合法
     */
    private boolean check(int type, int state, int x, int y){
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4; j++){
                if ( (shape[type][state][i][j] == 1) && ( (x+i>=COL) || (x+i<0 ) || (y+j>=ROW) || (map[x+i][y+j]==1) ) ) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 判斷遊戲是否結束
     */
    private boolean isGameOver(int type, int state, int x, int y){
        return !check(type, state, x, y);
    }

    /**
     * 新建方塊
     */
    private void newShape(){
        Random rand = new Random();
        if(newBegin){
            type = rand.nextInt(7); 
            state = rand.nextInt(4); 
            newBegin = false;
        }
        else{
            type = nextType;        
            state = nextState;
        }        
        nextType = rand.nextInt(7); 
        nextState = rand.nextInt(4);        
        x = 3;
        y = 0;
        // 如果遊戲已結束,則重新開始
        if(isGameOver(type, state, x, y)){            
            JOptionPane.showMessageDialog(f, "GAME OVER!");
            newGame();
        }
        paintArea();
        
    }

    /**
     * 新建遊戲
     */
    private void newGame(){
        newMap();
        score = 0;
        newBegin = true;
    }

    /**
     * 清空背景圖
     */
    private void newMap(){
        for(int i = 0; i < COL; i++){
            Arrays.fill(map[i],0);
        }        
    }

    /**
     * 消行
     */
    private void delLine(){
        boolean flag = true;
        int addScore = 0;
        for(int j = 0; j < ROW; j++){
            flag = true;
            for( int i = 0; i < COL; i++){
                if (map[i][j]==0){
                    flag = false;
                    break;
                }
            }
            if(flag){
                addScore += 10;
                for(int t = j; t > 0; t--){
                    for(int i = 0; i <COL; i++){
                        map[i][t] = map[i][t-1];                        
                    }
                }
            }        
        }
        score += addScore*addScore/COL;
    }

    /**
     * 計時器所用的事件監聽器
     */
    private class timerListener implements ActionListener{
        public void actionPerformed(ActionEvent e){
            if(check(type, state , x, y+1) ){
                y = y +1;
            }
            else{
               add(type, state, x, y);
               delLine();
               newShape();
            }
            paintArea();
        }
    }

    /**
     * 把shape存到map的add方法
     */
    private void add(int type, int state, int x, int y){
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4 ; j++){
                if((y+j<ROW)&&(x+i<COL)&&(x+i>=0)&&(map[x+i][y+j]==0)){
                    map[x+i][y+j]=shape[type][state][i][j];
                    mapColor[x+i][y+j]=color[isColor?type:DEFAULT];
                }
            }
        }
    }

    /**
     * 下面為四個方向鍵對應的方法
     */
    private void turn(){
        int tmpState = state;
        state = (state + 1)%4;
        if (!check(type,state, x, y )) {
            state = tmpState; //不能轉就什麼都不做           
        }
        paintArea();
    }

    private void left(){
        if(check(type,state, x-1, y)){
            --x;
        }
        paintArea();
    }

    private void right(){
        if (check(type,state, x+1, y)) {
            ++x;
        }
        paintArea();
    }

    private void down(){
        if (check(type,state, x, y+1)) {
            ++y;
        }
        //如果下不去則固定之
        else{
            add(type, state, x, y);
            delLine();
            newShape();
        }
        paintArea();
    }


    /**
     * 主函式
     */
    public static void main(String[] args){
        new KingTetris().init();
    }
}