繪制頁面
首先繪制指定寬和高的窗體
JFrame frame = new JFrame();frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);frame.setSize(514,595);frame.setTitle("石頭迷陣單機版v1.0");//想讓游戲一直在屏幕的最上層frame.setAlwaysOnTop(true);//想讓窗體開始時在屏幕的居中位置frame.setLocationRelativeTo(null);frame.setVisible(true);
這里的窗體就是上一章的代碼,其中為了讓游戲一直在我們屏幕的最上層,使用了setAlwaysOnTop方法;并且想讓游戲開始時就在屏幕的居中位置,使用了setLocationRelativeTo方法。
效果:
繪制背景
圖像用JLabel里的ImageIcon添加就可以,那么是先添加石頭方塊還是先添加背景圖呢
應該是先添加石頭方塊,因為在同一重疊區域內,后添加的JLabel會放在圖層最底下
確定第一塊石頭的坐標(46,50)和大小(100,100);我們就可以添加所有的石頭方塊了,先手動添加
public class GameWidget {public static void main(String[] args) {JFrame frame = new JFrame();frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);frame.setSize(514,595);frame.setTitle("石頭迷陣單機版v1.0");//想讓游戲一直在屏幕的最上層frame.setAlwaysOnTop(true);//想讓窗體開始時在屏幕的居中位置frame.setLocationRelativeTo(null);//取消默認布局frame.setLayout(null);//第一個方塊左邊是(50,90)JLabel label0 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\0.png"));label0.setBounds(50,90,100,100);frame.getContentPane().add(label0);JLabel label1 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\1.png"));label1.setBounds(150,90,100,100);frame.getContentPane().add(label1);JLabel label2 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\2.png"));label2.setBounds(250,90,100,100);frame.getContentPane().add(label2);JLabel label3 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\3.png"));label3.setBounds(350,90,100,100);frame.getContentPane().add(label3);JLabel label4 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\4.png"));label4.setBounds(50,190,100,100);frame.getContentPane().add(label4);JLabel label5 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\5.png"));label5.setBounds(150,190,100,100);frame.getContentPane().add(label5);JLabel label6 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\6.png"));label6.setBounds(250,190,100,100);frame.getContentPane().add(label6);JLabel label7 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\7.png"));label7.setBounds(350,190,100,100);frame.getContentPane().add(label7);JLabel label8 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\8.png"));label8.setBounds(50,290,100,100);frame.getContentPane().add(label8);JLabel label9 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\9.png"));label9.setBounds(150,290,100,100);frame.getContentPane().add(label9);JLabel label10 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\10.png"));label10.setBounds(250,290,100,100);frame.getContentPane().add(label10);JLabel label11 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\11.png"));label11.setBounds(350,290,100,100);frame.getContentPane().add(label11);JLabel label12 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\12.png"));label12.setBounds(50,390,100,100);frame.getContentPane().add(label12);JLabel label13= new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\13.png"));label13.setBounds(150,390,100,100);frame.getContentPane().add(label13);JLabel label14 = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\14.png"));label14.setBounds(250,390,100,100);frame.getContentPane().add(label14);JLabel label15= new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\15.png"));label15.setBounds(350,390,100,100);frame.getContentPane().add(label15);JLabel label = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\background.png"));label.setBounds(26,30,450,484);frame.getContentPane().add(label);//設置窗口可見frame.setVisible(true);
?效果為
?
代碼優化
二維數組加循環嵌套
我們可以將圖像從0到15共16個數字作為二維數組的索引,0,1,2,3為第一行;這樣我們在后續調用圖像的時候,圖像路徑直接調用data[i][j]就可以了。
JFrame frame = new JFrame();frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);frame.setSize(514,595);frame.setTitle("石頭迷陣單機版v1.0");//想讓游戲一直在屏幕的最上層frame.setAlwaysOnTop(true);//想讓窗體開始時在屏幕的居中位置frame.setLocationRelativeTo(null);//取消默認布局frame.setLayout(null);//用二維數組記錄圖像的idint[][] data= {{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};for(int i=0;i<4;i++){for(int j=0;j<4;j++){//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\"+data[i][j]+".png"));imagelabel.setBounds(100*j+50,100*i+90,100,100);frame.getContentPane().add(imagelabel);}}//加背景圖像JLabel bglabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\background.png"));bglabel.setBounds(26,30,450,484);frame.getContentPane().add(bglabel);//設置窗口可見frame.setVisible(true);
我們把行數看做i,列數看做j;可以看見我們每個方塊的坐標其實可以用j*100+50和i*100+50表示。
繼承優化
為了方便我們自己添加后續的功能,我們可以自己創建一個窗口類去繼承JFrame
public class MainFrame extends JFrame {}
?繼承好JFrame后,我們可以把之前的代碼粘貼過來,并且把所有關于窗體的代碼放在一個方法里面,所有繪制石頭的代碼放在一個方法里面
public void paintStone(){for(int i=0;i<4;i++){for(int j=0;j<4;j++){//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\"+data[i][j]+".png"));imagelabel.setBounds(100*j+50,100*i+90,100,100);frame.getContentPane().add(imagelabel);}}//加背景圖像JLabel bglabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\background.png"));bglabel.setBounds(26,30,450,484);frame.getContentPane().add(bglabel);}
這里我們會發現frame會報錯,但是不需要將其添加為成員變量;因為我們繼承了JFrame,JFrame類中的方法我們都有,所以我們可以將frame變成super,而且我們并沒有對父類JFrame中的方法進行重寫,所以super可以省略,最終兩個方法代碼為:
public class MainFrame extends JFrame {//用二維數組記錄圖像的idint[][] data= {{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};public void widgetInit(){setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);setSize(514,595);setTitle("石頭迷陣單機版v1.0");//想讓游戲一直在屏幕的最上層setAlwaysOnTop(true);//想讓窗體開始時在屏幕的居中位置setLocationRelativeTo(null);//取消默認布局setLayout(null);//設置窗口可見setVisible(true);}public void paintStone(){for(int i=0;i<4;i++){for(int j=0;j<4;j++){//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\"+data[i][j]+".png"));imagelabel.setBounds(100*j+50,100*i+90,100,100);getContentPane().add(imagelabel);}}//加背景圖像JLabel bglabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\background.png"));bglabel.setBounds(26,30,450,484);getContentPane().add(bglabel);}
?我們希望兩個方法+窗口visible顯示在這個Mainframe創建對象的時候就顯示,那么我們可以寫入構造函數中
public MainFrame() {widgetInit();paintStone();//設置窗口可見setVisible(true);}
我們在test文件中創建Mainframe的對象,進行測試,效果正常。
打亂石頭方塊
我們只需要對二維數組中的索引進行打亂就可以
使用random.nextInt(4)分別作為當前二維數組元素要交換的索引下標的行數和列數,遍歷一遍二維數組,進行交換就可以
public void dataInit(){Random rand = new Random();for(int i=0;i<4;i++){for(int j=0;j<4;j++){int x=rand.nextInt(4);int y=rand.nextInt(4);int temp=data[x][y];data[x][y]=data[i][j];data[i][j]=temp;}}}
效果為
移動業務
首先要給我們的窗體增加鍵盤輸入上下左右的監聽
//之前的鍵盤監聽格式是窗口對象frame.addKeyListener(KeyListener的實現類)//第一個this是代表本類MainFrame的對象//第二個this代表實現類KeyListener的對象this.addKeyListener(this);
像這段代碼中注釋所示,為了不讓我們的代碼更加繁瑣,我們可以用this(代表本類的對象)去分別代表窗體Mainframe類的對象以及實現類KeyListener的對象去加入鍵盤的監聽事件。
接下來系統會提示我們對實現類中的方法進行重寫,我們挑選KeyPressed方法進行重寫,并利用getKeyCode去調用我們要實現的移動業務方法move
public void move(int keyCode){if (keyCode == 37) {System.out.println("左移動業務代碼執行");} else if (keyCode == 38) {System.out.println("上移動業務代碼執行");} else if (keyCode == 39) {System.out.println("右移動業務代碼執行");} else if (keyCode == 40) {System.out.println("下移動業務代碼執行");}}@Overridepublic void keyPressed(KeyEvent e) {//判斷鍵盤輸入是否為上下左右,對應移動業務int keyCode = e.getKeyCode();move(keyCode);}
?尋找零號元素
public void dataInit(){Random rand = new Random();for(int i=0;i<4;i++){for(int j=0;j<4;j++){int x=rand.nextInt(4);int y=rand.nextInt(4);int temp=data[x][y];data[x][y]=data[i][j];data[i][j]=temp;}}//找到0號元素for(int i=0;i<4;i++){for(int j=0;j<4;j++){if(data[i][j]==0){xindex=i;yindex=j;break;}}}}
這里的兩個零號元素需要作為成員變量,因為還有move方法要調用
進行判斷并移動位置
public class MainFrame extends JFrame implements KeyListener {public MainFrame() {//之前的鍵盤監聽格式是窗口對象frame.addKeyListener(KeyListener的實現類)//第一個this是代表本類MainFrame的對象//第二個this代表實現類KeyListener的對象this.addKeyListener(this);//初始化窗口widgetInit();//打亂石頭順序dataInit();//繪制石頭方塊paintStone();//設置窗口可見setVisible(true);}//記錄零號元素int xindex;int yindex;//用二維數組記錄圖像的idint[][] data= {{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};public void dataInit(){Random rand = new Random();for(int i=0;i<4;i++){for(int j=0;j<4;j++){int x=rand.nextInt(4);int y=rand.nextInt(4);int temp=data[x][y];data[x][y]=data[i][j];data[i][j]=temp;}}//找到0號元素for(int i=0;i<4;i++){for(int j=0;j<4;j++){if(data[i][j]==0){xindex=i;yindex=j;break;}}}}public void widgetInit(){setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);setSize(514,595);setTitle("石頭迷陣單機版v1.0");//想讓游戲一直在屏幕的最上層setAlwaysOnTop(true);//想讓窗體開始時在屏幕的居中位置setLocationRelativeTo(null);//取消默認布局setLayout(null);}public void paintStone(){//為了刷新,我們要先清空之前的Label,要不然直接調用這個方法會放在圖層的最底下,不能顯示getContentPane().removeAll();for(int i=0;i<4;i++){for(int j=0;j<4;j++){//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\"+data[i][j]+".png"));imagelabel.setBounds(100*j+50,100*i+90,100,100);getContentPane().add(imagelabel);}}//加背景圖像JLabel bglabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\background.png"));bglabel.setBounds(26,30,450,484);getContentPane().add(bglabel);//remove后需要repaint進行label的刷新getContentPane().repaint();}public void move(int keyCode){if (keyCode == 37) {//判斷是不是在最右邊,不是的話,右邊的石頭方塊就會向左移動if(yindex!=3){int temp=data[xindex][yindex];data[xindex][yindex]=data[xindex][yindex+1];data[xindex][yindex+1]=temp;yindex++;}System.out.println("左移動業務代碼執行");} else if (keyCode == 38) {if(xindex!=3){int temp=data[xindex][yindex];data[xindex][yindex]=data[xindex+1][yindex];data[xindex+1][yindex]=temp;xindex++;}System.out.println("上移動業務代碼執行");} else if (keyCode == 39) {if(yindex!=0){int temp=data[xindex][yindex];data[xindex][yindex]=data[xindex][yindex-1];data[xindex][yindex-1]=temp;yindex--;}System.out.println("右移動業務代碼執行");} else if (keyCode == 40) {if(xindex!=0){int temp=data[xindex][yindex];data[xindex][yindex]=data[xindex-1][yindex];data[xindex-1][yindex]=temp;xindex--;}System.out.println("下移動業務代碼執行");}}@Overridepublic void keyPressed(KeyEvent e) {//判斷鍵盤輸入是否為上下左右,對應移動業務int keyCode = e.getKeyCode();move(keyCode);//移動后需要刷新paintStone();}//--------------------------------------------------
假如說我們進行上移動操作,那么我們的空白格需要和下面的石頭格子進行交換位置,那就要判斷xindex是不是已經是3了,如果是3,那么就撞墻了,不能再進行交換了。
除此之外,我們每進行一次move操作,就需要刷新一下頁面。
繪制頁面方法需要先對之前的label進行removeall操作,再進行白板添加,再進行repaint進行刷新。?
游戲判定勝利
添加作弊器
當我的getKeyCode為90也就是鍵入z的時候,我希望能夠直接讓所有的石頭塊為正常狀態
}else if (keyCode == 90) {data=new int[][]{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,0}};}
判斷勝利winJudge方法?
private boolean winJudge() {//將目前的data和windata進行對比for (int i=0;i<4;i++){for(int j=0;j<4;j++){if(data[i][j]!=windata[i][j]){return false;}}}//贏了就繪制勝利圖像,且應該結束游戲return true;}
我們先創建勝利狀態下的數組windata
int[][] windata={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,0}};
勝利狀態
我們現在需要在繪制頁面進行判斷,如果已經勝利了,加入勝利標志,而且不能再進行移動了
//如果勝利,我們需要先加入勝利的Labelif(winJudge()){//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\win.png"));imagelabel.setBounds(124,230,266,88);getContentPane().add(imagelabel);//此時游戲已經勝利,不應該再移動了}
public void move(int keyCode){//游戲勝利不能再移動了if(winJudge()){return;}
現在我們用作弊器測試一下效果
老鐵沒毛病?
統計步數
記錄一下我們用了多少步數,增加成員變量count。上下左右移動的時候都count++,最后在paint方法中加入我們的步數label
//左上角加入步數JLabel footcount =new JLabel("目前已用步數:"+count);footcount.setBounds(50,20,100,20);getContentPane().add(footcount);
重新游戲
在paint方法中加入JButton,需要取消默認布局,取消焦點
點擊之后,我們需要重置步數,重置數據,重新繪制
//添加按鈕JButton regamebtn =new JButton("重新開始");regamebtn.setBounds(350,20,100,20);getContentPane().add(regamebtn);regamebtn.setFocusable(false);//添加按鈕的點擊監聽regamebtn.addActionListener(this);
這里我們可以用內部匿名類,也可以再implements接口ActionListener去用this作為ActionListener的實現類對象,然后需要我們重寫方法,在重寫方法里面進行重置步數等命令
@Overridepublic void actionPerformed(ActionEvent e) {//重置步數count=0;//重置data數據dataInit();//重新繪制paintStone();}
運行效果
全體代碼
package yuhan;import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;public class MainFrame extends JFrame implements KeyListener, ActionListener {public MainFrame() {//之前的鍵盤監聽格式是窗口對象frame.addKeyListener(KeyListener的實現類)//第一個this是代表本類MainFrame的對象//第二個this代表實現類KeyListener的對象this.addKeyListener(this);//初始化窗口widgetInit();//打亂石頭順序dataInit();//繪制石頭方塊paintStone();//設置窗口可見setVisible(true);}//記錄零號元素int xindex;int yindex;//統計步數int count;//用二維數組記錄圖像的idint[][] data = {{0, 1, 2, 3},{4, 5, 6, 7},{8, 9, 10, 11},{12, 13, 14, 15}};int[][] windata = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};public void dataInit() {Random rand = new Random();for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {int x = rand.nextInt(4);int y = rand.nextInt(4);int temp = data[x][y];data[x][y] = data[i][j];data[i][j] = temp;}}//找到0號元素for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (data[i][j] == 0) {xindex = i;yindex = j;return;}}}}public void widgetInit() {setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);setSize(514, 595);setTitle("石頭迷陣單機版v1.0");//想讓游戲一直在屏幕的最上層setAlwaysOnTop(true);//想讓窗體開始時在屏幕的居中位置setLocationRelativeTo(null);//取消默認布局setLayout(null);}public void paintStone() {//為了刷新,我們要先清空之前的Label,要不然直接調用這個方法會放在圖層的最底下,不能顯示getContentPane().removeAll();//左上角加入步數JLabel footcount = new JLabel("目前已用步數:" + count);footcount.setBounds(50, 20, 100, 20);getContentPane().add(footcount);//添加按鈕JButton regamebtn = new JButton("重新開始");regamebtn.setBounds(350, 20, 100, 20);getContentPane().add(regamebtn);regamebtn.setFocusable(false);//添加按鈕的點擊監聽regamebtn.addActionListener(this);//如果勝利,我們需要先加入勝利的Labelif (winJudge()) {//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\win.png"));imagelabel.setBounds(124, 230, 266, 88);getContentPane().add(imagelabel);//此時游戲已經勝利,不應該再移動了}for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {//第一個方塊左邊是(50,90)JLabel imagelabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\" + data[i][j] + ".png"));imagelabel.setBounds(100 * j + 50, 100 * i + 90, 100, 100);getContentPane().add(imagelabel);}}//加背景圖像JLabel bglabel = new JLabel(new ImageIcon("D:\\Java資料\\資料\\進階篇\\day04\\資料\\image\\background.png"));bglabel.setBounds(26, 30, 450, 484);getContentPane().add(bglabel);//remove后需要repaint進行label的刷新getContentPane().repaint();}public void move(int keyCode) {//游戲勝利不能再移動了if (winJudge()) {return;}if (keyCode == 37) {//判斷是不是在最右邊,不是的話,右邊的石頭方塊就會向左移動if (yindex != 3) {int temp = data[xindex][yindex];data[xindex][yindex] = data[xindex][yindex + 1];data[xindex][yindex + 1] = temp;yindex++;count++;}System.out.println("左移動業務代碼執行");} else if (keyCode == 38) {if (xindex != 3) {int temp = data[xindex][yindex];data[xindex][yindex] = data[xindex + 1][yindex];data[xindex + 1][yindex] = temp;xindex++;count++;}System.out.println("上移動業務代碼執行");} else if (keyCode == 39) {if (yindex != 0) {int temp = data[xindex][yindex];data[xindex][yindex] = data[xindex][yindex - 1];data[xindex][yindex - 1] = temp;yindex--;count++;}System.out.println("右移動業務代碼執行");} else if (keyCode == 40) {if (xindex != 0) {int temp = data[xindex][yindex];data[xindex][yindex] = data[xindex - 1][yindex];data[xindex - 1][yindex] = temp;xindex--;count++;}System.out.println("下移動業務代碼執行");} else if (keyCode == 90) {data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};}}@Overridepublic void keyPressed(KeyEvent e) {//判斷鍵盤輸入是否為上下左右,對應移動業務int keyCode = e.getKeyCode();move(keyCode);//移動后需要刷新paintStone();}private boolean winJudge() {//將目前的data和windata進行對比for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (data[i][j] != windata[i][j]) {return false;}}}//贏了就繪制勝利圖像,且應該結束游戲return true;}//--------------------------------------------------@Overridepublic void keyReleased(KeyEvent e) {}@Overridepublic void keyTyped(KeyEvent e) {}@Overridepublic void actionPerformed(ActionEvent e) {//重置步數count = 0;//重置data數據dataInit();//重新繪制paintStone();}
}
package yuhan;public class test {public static void main(String[] args) {new MainFrame();}
}
?效果
至此,石頭迷陣游戲代碼編寫完畢,用了很多之前的類、接口、重寫方法、構造方法這些面向對象思想。?