首頁 > 軟體

Java實現經典遊戲推箱子的範例程式碼

2022-02-15 10:02:35

前言

《推箱子》推箱子是一個古老的遊戲,目的是在訓練你的邏輯思考能力。在一個狹小的倉庫中,要求把木箱放到指定的位置,稍不小心就會出現箱子無法移動或者通道被堵住的情況,所以需要巧妙的利用有限的空間和通道,合理安排移動的次序和位置,才能順利的完成任務。

遊戲是用java語言實現,採用了swing技術進行了介面化處理,設計思路用了物件導向思想。

主要需求

控制搬運工上下左右移動,來將箱子推到指定地點

主要設計

1、遊戲面板生成顯示

2、地圖生成演演算法

3、人物移動演演算法

4、播放背景音樂

5、箱子移動演演算法

6、全部箱子移動到指定位置,才算遊戲過關

功能截圖

遊戲開始

移動效果

遊戲過關

程式碼實現

核心類

public class GameFrame extends JFrame implements
        ActionListener, MouseListener, KeyListener {// 實現動作事件監聽器、滑鼠事件監聽器、鍵盤事件監聽器

    // 當前的關卡數,預設為第一關,從1開始計數
    private int grade = 1;
    // row,column記載人的位置,分別表示二維陣列中的行號和列號,即map[row][column]確定人的位置
    private int row = 7, column = 7;
    // leftX,leftY記載左上角圖片的位置,避免圖片從(0,0)座標開始,因為是圖片填充,從(0,0)開始不行
    private int leftX = 50, leftY = 50;
    // 記載地圖的總共有多少行、多少列
    private int mapRow = 0, mapColumn = 0;
    // 記載螢幕視窗的寬度和高度
    private int width = 0, height = 0;
    private boolean acceptKey = true;
    // 程式所需要用到的圖片
    private Image pics[] = null;// 圖片資料
    private byte[][] map = null;// 地圖資料
    private ArrayList list = new ArrayList();
    private SoundPlayerUtil soundPlayer;// 播放聲音工具類

    /* 常數,即遊戲中的資源 */
    private final static int WALL = 1;// 牆
    private final static int BOX = 2;// 箱子
    private final static int BOX_ON_END = 3;// 放到目的地的箱子
    private final static int END = 4;// 目的地
    private final static int MAN_DOWN = 5;// 向下的人
    private final static int MAN_LEFT = 6;// 向左的人
    private final static int MAN_RIGHT = 7;// 向右的人
    private final static int MAN_UP = 8;// 向上的人
    private final static int GRASS = 9;// 通道
    private final static int MAN_DOWN_ON_END = 10;// 站在目的地向下的人
    private final static int MAN_LEFT_ON_END = 11;// 站在目的地向左的人
    private final static int MAN_RIGHT_ON_END = 12;// 站在目的地向右的人
    private final static int MAN_UP_ON_END = 13;// 站在目的地向上的人
    private final static int MOVE_PIXEL = 30;// 表示每次移動30畫素

    /**
     * 在構造方法GameFrame0中,呼叫initMap()法來初始化本關grade遊戲地圖,清空悔棋信
     * 息列表list,同時播放MIDI背景音樂。
     */
    public GameFrame() {
        // 遊戲視窗的一些基本設定
        setTitle("推箱子游戲");
        setSize(600, 600);
        setVisible(true);
        setLocation(300, 20);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container contentPane = getContentPane();
        contentPane.setLayout(null);
        contentPane.setBackground(Color.black);
        // 其他設定,初始化視窗的寬度和高度賦值給width和height
        this.width = getWidth();
        this.height = getHeight();
        // 初始化圖片資源
        getPics();
        // 初始化地圖資料
        initMap();
        // 註冊事件監聽器
        setFocusable(true);
        addKeyListener(this);
        addMouseListener(this);
        // 播放音樂
        initSound();
    }

    /**
     * initMap()方法的作用是初始化本關grade 遊戲地圖,清空悔棋資訊列表list。 呼叫
     * getMapSizeAndPosition(方法獲取遊戲區域大小及顯示遊戲的左上角位置( leftX, leftY )。
     */
    public void initMap() {
        // 獲取當前關卡的地圖資料
        map = MapFactory.getMap(grade);
        // 清除上一關儲存的回退地圖資料,即清空list集合的內容
        list.clear();
        // 初始化地圖行列數和左上角起始座標位置
        getMapSizeAndPosition();
        // 獲取角色的座標位置
        getManPosition();
    }

    /**
     * getManPosition()方法的作用是獲取工人的當前位置(row,column)。
     */
    public void getManPosition() {
        // 即遍歷地圖陣列map中存在那個值等於MANXXX(MAN_DOWN表示向下的人;MAN_UP表示向上的人)的情況,即表示該位置是人站立的位置,這個由地圖資料掃描得出
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[0].length; j++) {
                if (map[i][j] == MAN_DOWN || map[i][j] == MAN_DOWN_ON_END
                        || map[i][j] == MAN_UP || map[i][j] == MAN_UP_ON_END
                        || map[i][j] == MAN_LEFT || map[i][j] == MAN_LEFT_ON_END
                        || map[i][j] == MAN_RIGHT || map[i][j] == MAN_RIGHT) {
                    // 儲存人的位置,i表示第幾行,j表示第幾列,而且是從0開始的
                    this.row = i;
                    this.column = j;
                    break;
                }
            }
        }
    }

    /**
     * getMapSizeAndPosition()方法用來獲取遊戲區域大小及顯示遊戲的左上角位置( lefX, leftY )。
     */
    private void getMapSizeAndPosition() {
        // 初始化mapRow和mapColumn,表示地圖的行列數
        this.mapRow = map.length;
        this.mapColumn = map[0].length;
        // 初始化leftX和leftY,即計算左上角的位置,
        this.leftX = (width - map[0].length * MOVE_PIXEL) / 2;
        this.leftY = (height - map.length * MOVE_PIXEL) / 2;
    }

    /**
     * getPics()方法用來載入要顯示的圖片
     */
    public void getPics() {
        // 建立長度為13的陣列,即有十三張圖片
        pics = new Image[13];
        // 然後迴圈將每張圖片讀取儲存到pics陣列中
        for (int i = 0; i < 13; i++) {
            pics[i] = Toolkit.getDefaultToolkit().getImage("src\images\pic_" + (i + 1) + ".png");
        }
    }

    /**
     * 初始化播放的音樂
     */
    public void initSound() {
        // 呼叫SoundPlayerUtil類中的方法播放音樂
        soundPlayer = new SoundPlayerUtil();
        soundPlayer.loadSound("src\sounds\music.wav");
        soundPlayer.playSound(true);// 迴圈播放
    }

    /**
     * grassOrEnd()方法判斷人所在的位置是通道GRASS還是目的地END
     *
     * @param man
     * @return
     */
    public byte grassOrEnd(byte man) {
        if (man == MAN_DOWN_ON_END || man == MAN_LEFT_ON_END || man == MAN_RIGHT_ON_END || man == MAN_UP_ON_END) {
            return END;
        }
        return GRASS;
    }

    /**
     * 人物向上移動
     */
    private void moveUp() {
        // 如果上一位是WALL,則不能移動
        if (map[row - 1][column] == WALL) {
            return;
        }
        // 如果上一位是BOX或BOX_ON_END,需要考慮上一位的上一位是什麼情況
        if (map[row - 1][column] == BOX || map[row - 1][column] == BOX_ON_END) {
            // 那麼就需要考慮上一位的上一位情況,若上上一位是END或GRASS,則向上一步,其他情況不用處理
            if (map[row - 2][column] == END || map[row - 2][column] == GRASS) {
                // 要保留當前資訊,以便回退上一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte boxTemp = (byte) ((byte) map[row - 2][column] == END ? BOX_ON_END : BOX);
                byte manTemp = (byte) (map[row - 1][column] == BOX ? MAN_UP : MAN_UP_ON_END);
                // 箱子變成temp,箱子往前移動一步
                map[row - 2][column] = boxTemp;
                // 人變成MAN_UP,往上走一步
                map[row - 1][column] = manTemp;
                // 將人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                row--;
            }
        } else {
            // 上一位為GRASS或END,無需考慮上上一步,其他情況不用處理
            if (map[row - 1][column] == GRASS || map[row - 1][column] == END) {
                // 保留當前這一步的資訊,以便回退上一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte temp = (byte) (map[row - 1][column] == END ? MAN_UP_ON_END : MAN_UP);
                // 人變成temp,人往上走一步
                map[row - 1][column] = temp;
                // 人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                row--;
            }
        }
    }

    /**
     * 人物向下移動,其中(row,column)是當前角色站的位置,而map[row,column]表示當前角色
     */
    private void moveDown() {
        // 如果下一位是WALL,則不能移動
        // 所以map[row+1][column]表示當前角色的下一步,也就是下一關影象塊
        if (map[row + 1][column] == WALL) {
            return;
        }
        // 如果下一位是箱子BOX或放到目的地的箱子BOX_ON_END(即如果下一位是箱子,而不管是什麼型別的箱子,都可以推動箱子),需要考慮下位的下一位是什麼情況
        if (map[row + 1][column] == BOX || map[row + 1][column] == BOX_ON_END) {
            // 那麼就需要考慮下一位的下一位情況(即箱子的下一位是什麼,決定著箱子是否可以向前移動),若下下一位是目的地END或通道GRASS,則表示箱子可以向下移動一步,其他情況不用處理
            // map[row+2][column]表示箱子的下一步是什麼
            if (map[row + 2][column] == END || map[row + 2][column] == GRASS) {
                // 下面的程式碼就是箱子向前移動一步,人移動原來箱子的位置
                // 要保留當前人和地圖資訊,以便回退下一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                // 判斷箱子的下一步是否是目的地,如果是目的地,那麼箱子的下一步就應該變成BOX_ON_END(放在目的地的箱子),如果不是目的地那應該還只是普通箱子BOX
                byte boxTemp = (byte) ((byte) map[row + 2][column] == END ? BOX_ON_END : BOX);
                // 判斷人的下一步是否是箱子
                byte manTemp = (byte) (map[row + 1][column] == BOX ? MAN_DOWN : MAN_DOWN_ON_END);
                // 箱子變成temp,箱子往下移動一步
                map[row + 2][column] = boxTemp;
                // 人變成MAN_UP,往下走一步
                map[row + 1][column] = manTemp;
                // 將人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                row++;
            }
        } else {
            // 執行到這裡,表示人的下一步不是箱子,那麼要麼是通道要麼是終點
            // 下一位為GRASS或END,無需考慮下下一步,其他情況不用處理
            if (map[row + 1][column] == GRASS || map[row + 1][column] == END) {
                // 保留當前這一步的資訊,以便回退下一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte temp = (byte) (map[row + 1][column] == END ? MAN_DOWN_ON_END : MAN_DOWN);
                // 人變成temp,人往下走一步
                map[row + 1][column] = temp;
                // 人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                row++;
            }
        }
    }

    /**
     * 人物向左移動
     */
    private void moveLeft() {
        // 如果左一位是WALL,則不能移動
        if (map[row][column - 1] == WALL) {
            return;
        }
        // 如果左一位是BOX或BOX_ON_END,需要考慮左一位的左一位是什麼情況
        if (map[row][column - 1] == BOX || map[row][column - 1] == BOX_ON_END) {
            // 那麼就需要考慮左一位的左一位情況,若左左一位是END或GRASS,則向左一步,其他情況不用處理
            if (map[row][column - 2] == END || map[row][column - 2] == GRASS) {
                // 要保留當前資訊,以便回退左一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte boxTemp = (byte) ((byte) map[row][column - 2] == END ? BOX_ON_END : BOX);
                byte manTemp = (byte) (map[row][column - 1] == BOX ? MAN_LEFT : MAN_LEFT_ON_END);
                // 箱子變成temp,箱子往前移動一步
                map[row][column - 2] = boxTemp;
                // 人變成MAN_UP,往左走一步
                map[row][column - 1] = manTemp;
                // 將人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                column--;
            }
        } else {
            // 左一位為GRASS或END,無需考慮左左一步,其他情況不用處理
            if (map[row][column - 1] == GRASS || map[row][column - 1] == END) {
                // 保留當前這一步的資訊,以便回退左一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte temp = (byte) (map[row][column - 1] == END ? MAN_LEFT_ON_END : MAN_LEFT);
                // 人變成temp,人往左走一步
                map[row][column - 1] = temp;
                // 人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                column--;
            }
        }
    }

    /**
     * 人物向右移動
     */
    private void moveRight() {
        // 如果右一位是WALL,則不能移動
        if (map[row][column + 1] == WALL) {
            return;
        }
        // 如果右一位是BOX或BOX_ON_END,需要考慮右位的右一位是什麼情況
        if (map[row][column + 1] == BOX || map[row][column + 1] == BOX_ON_END) {
            // 那麼就需要考慮右一位的右一位情況,若右右一位是END或GRASS,則向右一步,其他情況不用處理
            if (map[row][column + 2] == END || map[row][column + 2] == GRASS) {
                // 要保留當前資訊,以便回退右一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte boxTemp = (byte) ((byte) map[row][column + 2] == END ? BOX_ON_END : BOX);
                byte manTemp = (byte) (map[row][column + 1] == BOX ? MAN_RIGHT : MAN_RIGHT_ON_END);
                // 箱子變成temp,箱子往右移動一步
                map[row][column + 2] = boxTemp;
                // 人變成MAN_UP,往右走一步
                map[row][column + 1] = manTemp;
                // 將人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                column++;
            }
        } else {
            // 右一位為GRASS或END,無需考慮右右一步,其他情況不用處理
            if (map[row][column + 1] == GRASS || map[row][column + 1] == END) {
                // 保留當前這一步的資訊,以便回退右一步
                Map currMap = new Map(row, column, map);
                list.add(currMap);
                byte temp = (byte) (map[row][column + 1] == END ? MAN_RIGHT_ON_END : MAN_RIGHT);
                // 人變成temp,人往右走一步
                map[row][column + 1] = temp;
                // 人剛才站的地方變成GRASS或者END
                map[row][column] = grassOrEnd(map[row][column]);
                // 人離開後修改人的座標
                column++;
            }
        }
    }

    /**
     * 驗證玩家是否過關,如果有目的地END值或人直接站在目的地則沒有成功
     *
     * @return 如果已經通關則返回true,否則返回false
     */
    public boolean isFinished() {
        for (int i = 0; i < mapRow; i++) {
            for (int j = 0; j < mapColumn; j++) {
                if (map[i][j] == END || map[i][j] == MAN_DOWN_ON_END || map[i][j] == MAN_UP_ON_END || map[i][j] == MAN_LEFT_ON_END || map[i][j] == MAN_RIGHT_ON_END) {
                    return false;
                }
            }
        }
        return true;
    }

    // 使用雙緩衝技術解決動畫閃爍問題
    private Image iBuffer;
    private Graphics gBuffer;

    /**
     * 重寫繪製整個遊戲區域的圖形
     *
     * @param g
     */
    @Override
    public void paint(Graphics g) {
        if (iBuffer == null) {
            iBuffer = createImage(width, height);
            gBuffer = iBuffer.getGraphics();
        }
        // 清空螢幕原來的繪畫
        gBuffer.setColor(getBackground());
        gBuffer.fillRect(0, 0, width, height);
        for (int i = 0; i < mapRow; i++) {
            for (int j = 0; j < mapColumn; j++) {
                // 畫出地圖,i表示行數,j表示列數
                if (map[i][j] != 0) {
                    // 這裡要減1是因為圖片的名稱序號不對應,應該從0開始,但是從1開始的
                    gBuffer.drawImage(pics[map[i][j] - 1], leftX + j * MOVE_PIXEL, leftY + i * MOVE_PIXEL, 30, 30, this);
                }
            }
        }
        gBuffer.setColor(Color.RED);
        gBuffer.setFont(new Font("楷體_2312", Font.BOLD, 30));
        gBuffer.drawString("現在是第", 150, 140);
        gBuffer.drawString(String.valueOf(grade), 310, 140);
        gBuffer.drawString("關", 360, 140);
        g.drawImage(iBuffer, 0, 0, this);
        /* 下面的程式碼是未使用雙緩衝技術會導致動畫閃爍的程式碼 */
        /*g.clearRect(0, 0, width, height);
        for (int i = 0; i < mapRow; i++) {
            for (int j = 0; j < mapColumn; j++) {
                // 畫出地圖,i表示行數,j表示列數
                if (map[i][j] != 0) {
                    // 這裡要減1是因為圖片的名稱序號不對應,應該從0開始,但是從1開始的
                    g.drawImage(pics[map[i][j] - 1], leftX + j * MOVE_PIXEL, leftY + i * MOVE_PIXEL, 30, 30, this);
                }
            }
        }
        g.setColor(Color.RED);
        g.setFont(new Font("楷體_2312", Font.BOLD, 30));
        g.drawString("現在是第", 150, 140);
        g.drawString(String.valueOf(grade), 310, 140);
        g.drawString("關", 360, 140);*/
    }

    @Override
    public void actionPerformed(ActionEvent e) {

    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        // 當按鍵盤上的按鍵時觸發的事件
        switch (e.getKeyCode()) {
            case KeyEvent.VK_UP:// 上方向鍵
                moveUp();// 向上移動
                break;
            case KeyEvent.VK_DOWN:// 下方向鍵
                moveDown();// 向下移動
                break;
            case KeyEvent.VK_LEFT:// 左方向鍵
                moveLeft();// 向左移動
                break;
            case KeyEvent.VK_RIGHT:// 右方向鍵
                moveRight();// 向右移動
                break;
        }
        // 然後重新繪製介面
        repaint();
        // 在移動完成後可能已經通關,所以需要判斷是否通關
        if (isFinished()) {
            // 禁用按鍵
            acceptKey = false;
            // 判斷是否是最後一關,如果是則直接提示,如果不是則詢問是否要進入下一關
            if (grade == MapFactory.getCount()) {
                JOptionPane.showMessageDialog(this, "恭喜通過最後一關!");
            } else {
                // 提示進入下一關
                String msg = "恭喜通過第" + grade + "關!!!n是否要進入下一關?";
                int choice = JOptionPane.showConfirmDialog(null, msg, "過關", JOptionPane.YES_NO_OPTION);
                if (choice == 1) {
                    System.exit(0);
                } else if (choice == 0) {
                    // 進入下一關
                    acceptKey = true;
                    nextGrade();
                }
            }
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

    @Override
    public void mouseClicked(MouseEvent e) {
        // MouseEvent.BUTTON3表示滑鼠右鍵
        if (e.getButton() == MouseEvent.BUTTON3) {
            undo();
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {

    }

    @Override
    public void mouseReleased(MouseEvent e) {

    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }

    /**
     * 返回當前人的位置用getManX()方法和getManY()方法
     *
     * @return
     */
    public int getManX() {
        return row;
    }

    public int getManY() {
        return column;
    }

    /**
     * 返回當前關卡數
     *
     * @return
     */
    public int getGrade() {
        return grade;
    }

    /**
     * 返回當前關卡的地圖資訊
     *
     * @return
     */
    public byte[][] getMap() {
        return MapFactory.getMap(grade);
    }

    /**
     * 顯示提示資訊對話方塊
     *
     * @param str
     */
    public void displayToast(String str) {
        JOptionPane.showMessageDialog(null, str, "提示", JOptionPane.ERROR_MESSAGE);
    }

    /**
     * 復原移動操作
     */
    public void undo() {
        if (acceptKey) {
            if (list.size() > 0) {
                // 如果要復原,必須要走過
                // 考慮用棧更合適
                Map priorMap = (Map) list.get(list.size() - 1);
                this.map = priorMap.getMap();
                this.row = priorMap.getManX();
                this.column = priorMap.getManY();
                repaint();// 重新畫圖
                list.remove(list.size() - 1);
            } else {
                displayToast("不能再復原了!");
            }
        } else {
            displayToast("此關已完成,不能復原!");
        }
    }

    /**
     * 實現下一關的初始化,並且呼叫repaint()方法顯示遊戲介面
     */
    public void nextGrade() {
        // 初始化下一關的資料
        if (grade >= MapFactory.getCount()) {
            displayToast("恭喜你完成所有關卡!");
            acceptKey = false;
        } else {
            // 關卡數加1
            grade++;
            // 初始化下一關的地圖資料
            initMap();
            // 重新繪製畫面
            repaint();
            acceptKey = true;
        }
    }

    /**
     * 實現上一關初始化並且呼叫repaint()發顯示遊戲介面
     */
    public void priorGrade() {
        grade--;
        acceptKey = true;
        if (grade < 0) {
            grade = 0;
        }
        initMap();
        repaint();
    }
}

聲音播放類

public class SoundPlayerUtil {
    public File file;
    public AudioInputStream stream;
    public AudioFormat format;
    DataLine.Info info;
    Clip clip;

    /**
     * 載入聲音檔案,支援wav、mp3等聲音檔案
     *
     * @param filePath 聲音檔案的路徑
     */
    public void loadSound(String filePath) {
        file = new File(filePath);
        try {
            stream = AudioSystem.getAudioInputStream(file);
        } catch (UnsupportedAudioFileException | IOException e) {
            e.printStackTrace();
        }
        format = stream.getFormat();
    }

    /**
     * 播放音樂
     *
     * @param isLoop 表示是否迴圈播放音樂,如果傳入的是true則表示迴圈播放
     */
    public void playSound(boolean isLoop) {
        info = new DataLine.Info(Clip.class, format);
        try {
            clip = (Clip) AudioSystem.getLine(info);
            clip.open(stream);
        } catch (LineUnavailableException | IOException e) {
            e.printStackTrace();
        }
        if (isLoop) {
            clip.loop(Clip.LOOP_CONTINUOUSLY);// 新增該句程式碼可以迴圈播放
        }
        clip.start();
    }

}

總結

通過此次的《推箱子》遊戲實現,讓我對swing的相關知識有了進一步的瞭解,對java這門語言也有了比以前更深刻的認識。

java的一些基本語法,比如資料型別、運運算元、程式流程控制和陣列等,理解更加透徹。java最核心的核心就是物件導向思想,對於這一個概念,終於悟到了一些。

以上就是Java實現經典遊戲推箱子的範例程式碼的詳細內容,更多關於Java推箱子的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com