Java-黃金礦工(二)

小豹小小小發表於2024-05-22

目錄
  • 實現
    • 1-2 建立服務端和客戶端
    • 3.遊戲介面實現
      • (1)新建GameFrame類(遊戲介面)
      • (2)各小類
        • ①Bg.java(背景類)
        • ②Miner.java(礦工類)
        • ③Line.java(線類)
        • ④Hook.java(鉤子類)
        • ⑤Object.java(金塊)
      • (3)在GameFrame中呼叫各小類
        • ①建立變數
        • ②建立金塊
        • ③繪製畫面
        • ④建立傳遞變數
        • ⑤建立定時器
        • ⑥建立判斷贏家的方法
        • ⑦建立launch()方法,用於畫面顯示
      • (4)建立執行緒,用於開啟遊戲介面(OpenThread.java)
    • 4.在publicMeans.java中建立所需變數
    • 5.分別在服務端和客戶端中呼叫開啟遊戲介面的執行緒+訊息解析
    • 6.結束

實現

1-2 建立服務端和客戶端

Java-黃金礦工(一) https://www.cnblogs.com/xbxxx/p/18207095

3.遊戲介面實現

主要使用自帶的paint方法畫出來的,利用repaint方法一直重繪重新整理介面。

(1)新建GameFrame類(遊戲介面)

繼承JFrame。

public class GameFrame extends JFrame {}

(2)各小類

GameGrame類為遊戲介面主類,以下類均會被在GameGrame類中呼叫,因其中引用變數互有穿插,可能會有些混亂,以下直接放入各個被呼叫類的原始碼,原始碼後會有簡要註釋。

①Bg.java(背景類)

主要把up.jpg和down.jpg放入介面中,另外加上剩餘時間和每個礦工已獲得的金錢。

點選檢視程式碼
import javax.swing.*;
import java.awt.*;

public class Bg {//背景類
    Image bgUp = Toolkit.getDefaultToolkit().getImage("imgs/Up.jpg");//上方
    Image bgDown = Toolkit.getDefaultToolkit().getImage("imgs/down.jpg");//下方
    void paintSelf(Graphics g){
        g.drawImage(bgUp,0,30,null);//(0,30)在視窗裡面的座標
        g.drawImage(bgDown,0,151,null);//在視窗裡面的座標
        g.setColor(new Color(143,112,1));//設定字型顏色
        g.setFont(new Font("幼圓",Font.BOLD,20));//設定字型“幼圓”,加粗,字號20
        //下面是寫金錢和倒數計時的
        if (publicMeans.isServer){//服務端就直接寫Miner.money的
            g.drawString("金錢:" + Miner.money1,275,60);
            g.drawString("金錢:" + Miner.money2,609,60);//495+114=609
            g.setFont(new Font("幼圓",Font.BOLD,20));//倒數計時的字型
            g.drawString("時間:" + GameFrame.ss + "秒",800,60);//服務端就直接呼叫那個ss
        }
        else {//客戶端就寫publicMeans裡面的
            g.drawString("金錢:" + publicMeans.money1,275,60);
            g.drawString("金錢:" + publicMeans.money2,609,60);//495+114=609
            g.setFont(new Font("幼圓",Font.BOLD,20));
            g.drawString("時間:" + publicMeans.timeSs + "秒",800,60);
        }
    }
}

②Miner.java(礦工類)

基本為礦工的屬性,比如礦工的狀態(狀態1、狀態2、狀態3)和礦工的錢。

點選檢視程式碼
import java.awt.*;

public class Miner {//礦工
    public static int money1 = 0,money2 = 0;//礦工1和2的錢
    public static int state1 = 1,state2 = 1;//礦工1和2的狀態,3種狀態
    private Image minerState1 = Toolkit.getDefaultToolkit().getImage("imgs/minerState1.jpg");
    private Image minerState2 = Toolkit.getDefaultToolkit().getImage("imgs/minerState2.jpg");
    private Image minerState3 = Toolkit.getDefaultToolkit().getImage("imgs/minerState3.gif");
    //狀態1:普通狀態,手在上,沒有放線
    //狀態2:放線狀態,手在下,正在放線
    //狀態3:是個動圖,這個裡面好像看不了,是抓住金塊後,往上拉
    void paintSelf(Graphics g){
        if (publicMeans.isServer){
            if (state1 == 1){
                g.drawImage(minerState1,372,51,null);
            } else if (state1 == 2) {//礦工也變了
                g.drawImage(minerState2,372,51,null);
            } else if (state1 == 3) {
                g.drawImage(minerState3,372,51,null);
            }

            if (state2 == 1){
                g.drawImage(minerState1,495,51,null);
            } else if (state2 == 2) {
                g.drawImage(minerState2,495,51,null);
            } else if (state2 == 3) {
                g.drawImage(minerState3,495,51,null);
            }
        }
        else{
            if (publicMeans.state_miner1 == 1){
                g.drawImage(minerState1,372,51,null);
            } else if (publicMeans.state_miner1 == 2) {
                g.drawImage(minerState2,372,51,null);
            } else if (publicMeans.state_miner1 == 3) {
                g.drawImage(minerState3,372,51,null);
            }

            if (publicMeans.state_miner2 == 1){
                g.drawImage(minerState1,495,51,null);
            } else if (publicMeans.state_miner2 == 2) {
                g.drawImage(minerState2,495,51,null);
            } else if (publicMeans.state_miner2 == 3) {
                g.drawImage(minerState3,495,51,null);
            }
        }
    }
}

③Line.java(線類)

為礦工下放的線的基本屬性,比如座標、角度、方向、狀態(搖擺、抓取、未抓到收回、抓取收回)等。

點選檢視程式碼
import java.awt.*;

public class Line {//線
    GameFrame frame;
    Line(GameFrame frame){this.frame = frame;}

    //判斷線是否觸碰到金塊,若觸碰到,則線的狀態為3(抓取收回),金塊的狀態為1/2(被礦工1或2抓住)
    //只有在金塊的狀態為0的情況下,金塊才可以被抓取
    void logic(){
        for(Object obj:this.frame.objectList){//遍歷金子
            if (obj.flag == 0){//如果線的末端座標碰到金子了 此時線的狀態為1(抓取狀態)時才可以抓到金子
                if (endX1 > obj.x && endX1 < obj.x + obj.width && endY1 > obj.y && endY1 < obj.y + obj.height && Line.state1 == 1){
                    state1 = 3;//就改線的狀態 抓取收回=3
                    obj.flag = 1;//改金子的狀態 1:被礦工1抓住
                }
                if (endX2 > obj.x && endX2 < obj.x + obj.width && endY2 > obj.y && endY2 < obj.y + obj.height && Line.state2 == 1){
                    state2 = 3;
                    obj.flag = 2;//這裡是礦工2抓住
                }
            }
        }
    }

    public static int x1 = 410,y1 = 117,endX1,endY1;//礦工1的起點座標和終點座標
    public static int x2 = 530,y2 = 117,endX2,endY2;//礦工2的起點座標和終點座標
    public static double length1 = 50, length2 = 50;//線長
    public static double angle1 = 0.9, angle2 = 0.1;//角度,與x軸的夾角
    public static int dir1 = -1, dir2 = 1;//方向:順時針=1 逆時針=-1
    public static int state1 = 0, state2 = 0;//線的狀態:搖擺=0 抓取=1 收回(未抓到)=2 抓取收回=3
    void paintSelf(Graphics g){
        if (publicMeans.isServer){
            logic();//看這個方法 一直重繪,一直重繪
            g.setColor(new Color(51,51,51));//線的顏色
            switch (state1){
                case 0://搖擺(線與x軸的角度發生變化),此時就是狀態0
                    if (angle1 < 0.1) { dir1 = 1; } else if (angle1 > 0.9) { dir1 = -1; }
                    angle1 += 0.005 * dir1;//角度每次都變化一點點,因為畫面一直在重繪,就是repaint
                    lines1(g);//看這個,這會就是畫好線了,只要是線的狀態是0,就一直在搖擺
                    break;
                case 1://抓取(線變長),如果線的末端座標在窗體內,則繼續延長  狀態1,去抓取,線延長  現在就去抓取了
                    endX1 = (int)(x1 + length1 * Math.cos(angle1 * Math.PI));
                    endY1 = (int)(y1 + length1 * Math.sin(angle1 * Math.PI));
                    if (endX1>0 && endX1<985 && endY1<719){//這裡的985*719是視窗大小,如果末端座標沒有到達視窗邊緣,就會一直延長
                        length1 += 10;
                        lines1(g);//畫線
                    }
                    else{ state1 = 2; }//如果到達視窗邊緣了,就收回,即狀態2,沒有抓到的收回線
                    break;
                case 2://收回(線變短) 沒有抓到金塊
                    Miner.state1 = 3;//此時礦工狀態變為3,拉回的動態圖
                    if (length1 > 50){//為什麼>50,因為線的初始長度是50,線的狀態為0時,線長一直是50在搖擺
                        length1 -= 10;//線長度減小
                        lines1(g);//畫線
                    }
                    else{ state1 = 0; Miner.state1 = 1; }//如果到了50,就改變線的狀態為0,搖擺狀態,同時改變礦工的狀態為1,普通狀態
                    break;
                case 3://抓取收回(線變短,令金塊的位置=線的末端座標,這樣可以使金塊跟著線走)
                    //狀態3,抓到金塊了,線收回,被抓到的金塊的座標也跟著變化
                    if (length1 > 50){//一樣,線長>50就一直往回收,去看金塊類,回到線這裡
                        for(Object obj:this.frame.objectList){//遍歷剛才儲存的金塊集合
                            if (obj.flag == 1){//金塊狀態是1,代表金塊被礦工1抓住
                                Miner.state1 = 3;//礦工1要往上拉,礦工1的狀態變成3,動圖的那個
                                if(obj.width == 133){//如果金塊的寬是133,代表是大金子
                                    obj.x = endX1 - 66;//就把大金子的座標放線上的末端,為什麼要-66,線的末端要拉住金子的中間部分
                                    obj.y = endY1;
                                    length1 -= 0.5;//大金子比較重,所以線收的比較慢,線的長度也要減小的小一點
                                    lines1(g);//畫線
                                }
                                else if(obj.width == 72){
                                    obj.x = endX1 - 36;
                                    obj.y = endY1;
                                    length1 -= 1;//這裡是中金子,線回收的比大金子快
                                    lines1(g);//畫線
                                }
                                else if (obj.width == 24) {
                                    obj.x = endX1 - 12;
                                    obj.y = endY1;
                                    length1 -= 1.2;//這裡是小金子,線回收的比中金子快
                                    lines1(g);//畫線
                                }
                                //如果length小於初始長度了,則把金塊扔出去,即更改金塊的座標為畫面外
                                //同時修改金塊的狀態為3(金塊抓取完成被扔出)
                                //同時修改線的狀態0(搖擺)
                                if(length1 <= 50){//回收的過程中,檢測到length<50了,
                                    obj.x = -150;
                                    obj.y = -150;//金子座標放在畫面外
                                    obj.flag = 3;//金子狀態改為3=抓取完成被扔出
                                    state1 = 0;//線的狀態為0,搖擺
                                    Miner.state1 = 1;//礦工1的狀態變為1,普通狀態
                                    Miner.money1 += obj.money;//把錢新增到礦工1的錢裡面
                                }
                            }
                        }
                    }
                    break;
            }
            switch (state2){//這裡是線2 基本和線1一樣
                case 0:
                    if (angle2 < 0.1) { dir2 = 1; } else if (angle2 > 0.9) { dir2 = -1; }
                    angle2 += 0.005 * dir2;
                    lines2(g);
                    break;
                case 1:
                    endX2 = (int)(x2 + length2 * Math.cos(angle2 * Math.PI));
                    endY2 = (int)(y2 + length2 * Math.sin(angle2 * Math.PI));
                    if (endX2>0 && endX2<985 && endY2<719){
                        length2 += 10;
                        lines2(g);
                    }
                    else{ state2 = 2; }
                    break;
                case 2:
                    Miner.state2 = 3;
                    if (length2 > 50){
                        length2 -= 10;
                        lines2(g);
                    }
                    else{ state2 = 0; Miner.state2 = 1; }
                    break;
                case 3:
                    if (length2 > 50){
                        Miner.state2 = 3;
                        for(Object obj:this.frame.objectList){
                            if (obj.flag == 2){
                                if (obj.width == 133){
                                    obj.x = endX2 - 66;
                                    obj.y = endY2;
                                    length2 -= 0.5;
                                    lines2(g);
                                }
                                else if(obj.width == 72){
                                    obj.x = endX2 - 36;
                                    obj.y = endY2;
                                    length2 -= 1;
                                    lines2(g);
                                }
                                else if(obj.width == 24){
                                    obj.x = endX2 - 12;
                                    obj.y = endY2;
                                    length2 -= 0.8;
                                    lines2(g);
                                }
                                if (length2 <= 50){
                                    obj.x = -150;
                                    obj.y = -150;
                                    obj.flag = 3;
                                    state2 = 0;
                                    Miner.state2 = 1;
                                    Miner.money2 += obj.money;
                                }
                            }
                        }
                    }
                    break;
            }
        }
        else{
            g.drawLine(x1,y1,publicMeans.endX1,publicMeans.endY1);
            g.drawLine(x1-1,y1,publicMeans.endX1-1,publicMeans.endY1);
            g.drawImage(hook,(int)(x1+publicMeans.length1*Math.cos(publicMeans.angle1*Math.PI)-10),
                    (int)(y1+publicMeans.length1*Math.sin(publicMeans.angle1*Math.PI)-10),null);
            g.drawLine(x2,y2,publicMeans.endX2,publicMeans.endY2);
            g.drawLine(x2-1,y2,publicMeans.endX2-1,publicMeans.endY2);
            g.drawImage(hook,(int)(x2+publicMeans.length2*Math.cos(publicMeans.angle2*Math.PI)-10),
                    (int)(y2+publicMeans.length2*Math.sin(publicMeans.angle2*Math.PI)-10),null);
        }
    }

    private Image hook = Toolkit.getDefaultToolkit().getImage("imgs/hookBall.png");
    void lines1(Graphics g){//獲取末端座標,礦工1的線
        endX1 = (int)(x1 + length1 * Math.cos(angle1 * Math.PI));
        endY1 = (int)(y1 + length1 * Math.sin(angle1 * Math.PI));//獲取到了線的末端座標,下面就是畫線了
        //兩句g.drawLine為了加粗線
        g.drawLine(x1,y1,endX1,endY1);
        g.drawLine(x1-1,y1,endX1-1,endY1);
        //畫粉色小球,小球大小是20*20
        g.drawImage(hook,(int)(x1+length1*Math.cos(angle1*Math.PI)-10),(int)(y1+length1*Math.sin(angle1*Math.PI)-10),null);
    }

    void lines2(Graphics g){//畫線2的,就是礦工2的線
        endX2 = (int)(x2 + length2 * Math.cos(angle2 * Math.PI));
        endY2 = (int)(y2 + length2 * Math.sin(angle2 * Math.PI));
        g.drawLine(x2,y2,endX2,endY2);
        g.drawLine(x2-1,y2,endX2-1,endY2);
        g.drawImage(hook,(int)(x2+length2*Math.cos(angle2*Math.PI)-10),(int)(y2+length2*Math.sin(angle2*Math.PI)-10),null);
    }
}

④Hook.java(鉤子類)

點選檢視程式碼
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Hook {
    private BufferedImage hook1,hook2;
    {
        try {
            hook1 = ImageIO.read(new File("imgs\\hook.png"));
            hook2 = ImageIO.read(new File("imgs\\hook.png"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    int w = hook1.getWidth();
    int h = hook1.getHeight();
    public void paintSelf1(Graphics g)
    {
        hookMain(g,hook1,Line.endX1,Line.endY1,Line.angle1);
    }
    public void paintSelf2(Graphics g){
        hookMain(g,hook2,Line.endX2,Line.endY2,Line.angle2);
    }


    private void hookMain(Graphics g,BufferedImage hook,int x,int y,double angle){
        Graphics2D g2 = (Graphics2D)g;
        g2.translate(x - 22,y);
        g2.rotate(angle,w >> 1,h >> 1);
        g2.drawImage(hook,0,0,w,h,0,0,w,h,null);
        g2.translate(-x + 22,-y);
    }
}

⑤Object.java(金塊)

點選檢視程式碼
import java.awt.*;

public class Object {//這裡是新建一個類,代表金塊,下面是金子的屬性,座標,尺寸,圖片,價值就是錢,還有flag標誌
    public int x,y;//座標
    public int width,height;//尺寸
    public Image img;//圖片
    public int money = 0;
    public int flag;//0=能移動 1=被礦工1抓住 2=被礦工2抓住 3=抓取完成被扔出
    void paintSelf(Graphics g){
        g.drawImage(img,x,y,null);
    }
    public Rectangle getRec(){
        return new Rectangle(x,y,width,height);
    }
    public static Image bigGold = Toolkit.getDefaultToolkit().getImage("imgs/bigGold.png");
    public static Image middleGold = Toolkit.getDefaultToolkit().getImage("imgs/middleGold.png");
    public static Image smallGold = Toolkit.getDefaultToolkit().getImage("imgs/smallGold.png");
}

class BigGold extends Object{//大金塊繼承上面的金塊類

    BigGold(){//Math.random()是[0,1]的隨機數
        //金塊位置:地下的下半部分x∈[0,852],y∈[420,598]
        //橫座標 852=985-133:985是視窗寬度,133是金塊寬度,座標是指左上角座標,所以金塊座標不能超過985-133
        //如果超過了,就會超出畫面
        //縱座標同理
        int a = (int)(Math.random()*852);
        int b = (int)(Math.random()*178+420);//縱座標 719-121=598 598/2=299 719-299=420 598-420=178
        int c = 133;//寬度
        int d = 121;//高度

        this.x = a; this.y = b; this.width = c; this.height = d;
        this.img = Toolkit.getDefaultToolkit().getImage("imgs/bigGold.png");
        this.flag = 0;//初始為0,代表金塊可以被抓取
        this.money = 120;//大金子價值120元
    }
}
//下面的中等金塊和小金子同理
class MiddleGold extends Object{

    MiddleGold(){
        //金塊位置:地下的下半部分x∈[0,913],y∈[151,653]
        int a = (int)(Math.random()*913);//橫座標 985-72=913
        int b = (int)(Math.random()*477+176);//縱座標 719-66=653 653-176=477
        int c = 72;//寬度
        int d = 66;//高度

        this.x = a; this.y = b; this.width = c; this.height = d;
        this.img = Toolkit.getDefaultToolkit().getImage("imgs/middleGold.png");
        this.flag = 0;
        this.money = 60;//中金子60元
    }
}

class SmallGold extends Object{

    SmallGold(){
        //金塊位置:地下的下半部分x∈[0,961],y∈[151,697]
        int a = (int)(Math.random()*961);//橫座標 985-24=961
        int b = (int)(Math.random()*521+176);//縱座標 719-22=697 697-176=521
        int c = 24;//寬度
        int d = 22;//高度

        this.x = a; this.y = b; this.width = c; this.height = d;
        this.img = Toolkit.getDefaultToolkit().getImage("imgs/smallGold.png");
        this.flag = 0;
        this.money = 20;//小金子20元
    }
}

(3)在GameFrame中呼叫各小類

①建立變數

    Bg bg = new Bg();//背景類
    Miner miner = new Miner();//礦工
    Line line = new Line(this);//線,這個東西比較多 end
    Image canvasImage;
    BigGold bigGold;//大金塊
    MiddleGold middleGold;//中等金塊
    SmallGold smallGold;//小金子
    List<Object> objectList = new ArrayList<>();//儲存金塊、石塊

②建立金塊

點選檢視程式碼
    private void CreateGold()//這個是把建立金子放在了方法裡面
    {//新建list集合,放置金子
        //這裡我隨便設的,大金子最多5個,中金子最多8個,小金子最多10個
        for (int i = 0; i < 5; i++){
            bigGold = new BigGold();//新建大金子
            for(Object obj:objectList){//需要遍歷集合裡的所有金塊
                if (bigGold.getRec().intersects(obj.getRec())){//這個是判斷兩個金塊有沒有重疊
                    isOverlap = true;//如果有重疊了,就isOverlap=true
                    break;//跳出
                }
            }
            //遍歷完成後,沒有發現重疊,就把金子放到集合裡面
            if (!isOverlap){objectList.add(bigGold);}
            else{isOverlap = false;}//重置這個isOverlap,因為下面的中金子和小金子還要用它
        }
        for(int i = 0; i < 8; i++){
            middleGold = new MiddleGold();
            for(Object obj:objectList){
                if (middleGold.getRec().intersects(obj.getRec())){
                    isOverlap = true;
                    break;
                }
            }
            if (!isOverlap) {objectList.add(middleGold);}
            else{isOverlap = false;}
        }
        for(int i = 0; i < 10; i++){
            smallGold = new SmallGold();
            for(Object obj:objectList){
                if (smallGold.getRec().intersects(obj.getRec())){
                    isOverlap = true;
                    break;
                }
            }
            if (!isOverlap) {objectList.add(smallGold);}
            else{isOverlap = false;}
        }
        //這裡的中金子和小金子一樣,和大金子一樣
    }

③繪製畫面

    @Override
    public void paint(Graphics g) {//繪製畫面  剛才的repaint是一直走的這個paint方法
        canvasImage = this.createImage(985,719);//這裡是新建一個畫布,把所有的東西放在上面
        Graphics gCanvasImage = canvasImage.getGraphics();
        bg.paintSelf(gCanvasImage);//把背景放在上面
        miner.paintSelf(gCanvasImage);//礦工
        line.paintSelf(gCanvasImage);//線
        //金子 這裡的金子繪製,服務端就直接用objectList
        if (publicMeans.isServer){
            for (Object obj:objectList){
                obj.paintSelf(gCanvasImage);
            }
        }
        else {//客戶端就使用publicMeans.gold
            for (Object obj:publicMeans.gold){
                obj.paintSelf(gCanvasImage);
            }
        }

        g.drawImage(canvasImage,0,0,null);//把畫布畫出來
    }

④建立傳遞變數

點選檢視程式碼
    //第1套
    public static int state_miner1 = 0, state_miner2 = 0;
    public static int state_line1 = 0, endX1 = 0, endY1 = 0, dir1 = -1;
    public static int state_line2 = 0, endX2 = 0, endY2 = 0, dir2 = 1;
    public static double angle1 = 0.9, length1 = 50;
    public static double angle2 = 0.1, length2 = 50;
    public static List<Object> gold = new ArrayList<>();
    public static int money1 = 0, money2 = 0;
    public static int timeSs = 0;
    //第2套
    public static int Last_state_miner1 = Integer.MAX_VALUE, Last_state_miner2 = Integer.MAX_VALUE;
    public static int Last_state_line1 = Integer.MAX_VALUE, Last_endX1 = Integer.MAX_VALUE, Last_endY1 = Integer.MAX_VALUE, Last_dir1 = Integer.MAX_VALUE;
    public static int Last_state_line2 = Integer.MAX_VALUE, Last_endX2 = Integer.MAX_VALUE, Last_endY2 = Integer.MAX_VALUE, Last_dir2 = Integer.MAX_VALUE;
    public static double Last_angle1 = Double.MAX_VALUE, Last_length1 = Double.MAX_VALUE;
    public static double Last_angle2 = Double.MAX_VALUE, Last_length2 = Double.MAX_VALUE;
    public static List<Object> Last_gold = new ArrayList<>();
    public static int Last_money1 = Integer.MAX_VALUE, Last_money2 = Integer.MAX_VALUE;
    public static int Last_timeSs = Integer.MAX_VALUE;//這裡把last的初始值設定為int的最大值
    //因為在第一次進入介面時,肯定要把所有的資料傳送給客戶端一遍,然後再傳送變化的值
    private void UpdateThis(){
        //是把當前的所有狀態(礦工、線、錢和金子)都更新到第一套變數裡面 這一次的資料
        state_miner1 = Miner.state1; state_miner2 = Miner.state2;
        state_line1 = Line.state1; endX1 = Line.endX1; endY1 = Line.endY1; dir1 = Line.dir1;
        state_line2 = Line.state2; endX2 = Line.endX2; endY2 = Line.endY2; dir2 = Line.dir2;
        angle1 = Line.angle1; length1 = Line.length1;
        angle2 = Line.angle2; length2 = Line.length2;
        money1 = Miner.money1; money2 = Miner.money2;
        timeSs = ss;
        gold.clear();
        for (Object obj:objectList){gold.add(obj);}
    }
    private void UpdateLast(){
        //這裡是把第一套更新到第二套裡面 Last是指上一次的資料
        Last_state_miner1 = state_miner1; Last_state_miner2 = state_miner2;
        Last_state_line1 = state_line1; Last_endX1 = endX1; Last_endY1 = endY1; Last_dir1 = dir1;
        Last_state_line2 = state_line2; Last_endX2 = endX2; Last_endY2 = endY2; Last_dir2 = dir2;
        Last_angle1 = angle1; Last_length1 = length1;
        Last_angle2 = angle2; Last_length2 = length2;
        Last_money1 = money1; Last_money2 = money2;
        Last_timeSs = timeSs;
        Last_gold.clear();
        for (Object obj1:gold){Last_gold.add(obj1);}
    }
    //重新整理資料,如果不重新整理資料,再次點選開始遊戲時,畫面會出現問題
    public void IniData(){
        Miner.state1 = 1;Miner.state2 = 1;Miner.money1 = 0;Miner.money2 = 0;
        objectList.clear();
        Line.state1 = 0;Line.length1 = 50;Line.angle1 = 0.9;Line.dir1 = -1;
        Line.state2 = 0;Line.length2 = 50;Line.angle2 = 0.1;Line.dir2 = 1;

        gold.clear();

        Last_state_miner1 = Integer.MAX_VALUE; Last_state_miner2 = Integer.MAX_VALUE;
        Last_state_line1 = Integer.MAX_VALUE; Last_endX1 = Integer.MAX_VALUE; Last_endY1 = Integer.MAX_VALUE; Last_dir1 = Integer.MAX_VALUE;
        Last_state_line2 = Integer.MAX_VALUE; Last_endX2 = Integer.MAX_VALUE; Last_endY2 = Integer.MAX_VALUE; Last_dir2 = Integer.MAX_VALUE;
        Last_angle1 = Double.MAX_VALUE; Last_length1 = Double.MAX_VALUE;
        Last_angle2 = Double.MAX_VALUE; Last_length2 = Double.MAX_VALUE;
        Last_gold.clear();
        Last_money1 = Integer.MAX_VALUE; Last_money2 = Integer.MAX_VALUE;
        Last_timeSs = Integer.MAX_VALUE;

        publicMeans.gold.clear();
        publicMeans.timeSs = -1;

        time = 60;
        isFirst = true;
        num = 0;
    }

⑤建立定時器

    //現在判斷遊戲是否結束 :1.倒數計時結束 2.金子被抓完
    public static int ss;
    private static Timer timer = null;//timer是定時器
    private static void timer(){
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                time--;
                ss = time % 60;//ss就是剩餘時間
                if (ss == 0){
                    //如果ss=0了,說明倒數計時結束,遊戲結束 就是這裡
                    Server.Send("遊戲結束");
                    publicMeans.isEnd = true;
                    timer.cancel();//停止這個定時器
                }
            }
        },0,1000);//1000是指1000ms毫秒,也就是1秒執行1次
    }

⑥建立判斷贏家的方法

    public static String winner = "null";
    public static void JudgeWinner(){
        //判斷贏家
        if (publicMeans.isServer){
            //服務端就直接透過Miner.money判斷
            if (Miner.money1 > Miner.money2){
                winner = "玩家1獲勝。";
            } else if (Miner.money1 < Miner.money2) {
                winner = "玩家2獲勝。";
            } else {
                winner = "平手。";
            }
            Server.textArea.append("遊戲結束," + winner + "\r\n");//列印到服務端的文字框中
        }
        else {
            //客戶端就需要判斷publicMeans裡面的變數
            if (publicMeans.money1 > publicMeans.money2){
                winner = "玩家1獲勝。";
            } else if (publicMeans.money1 < publicMeans.money2) {
                winner = "玩家2獲勝。";
            } else {
                winner = "平手。";
            }
            Client.textArea.append("遊戲結束," + winner + "\r\n");//列印到客戶端的文字框中
        }
    }

⑦建立launch()方法,用於畫面顯示

其中包括
按鍵的判斷:按方向鍵的下“↓”,礦工就會放鉤子
變數的傳遞:主要是服務端向客戶端傳遞,客戶端透過這些被傳遞的資料進行畫面繪製

點選檢視程式碼
    private boolean isFirst = true;
    //倒數計時
    private static int time = 60;//秒
    private static int num = 0;//新建一個int變數,記錄一下金子集合也就是objectList裡面有幾個被抓完扔出了
    void launch(){//publicMeans.isServer在一開始就已經判斷過了
        this.setVisible(true);//視窗是否可見
        this.setBounds(publicMeans.GameWinX,publicMeans.GameWinY,publicMeans.GameWinW,publicMeans.GameWinH);
        //如果是服務端,就設定服務端的視窗標題為"黃金礦工聯機版(server)"
        if (publicMeans.isServer){this.setTitle("黃金礦工聯機版(server)");}
        //如果是客戶端,就為"黃金礦工聯機版(client)"
        else {this.setTitle("黃金礦工聯機版(client)");}
        this.setResizable(false);//視窗不能更改大小
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);//關閉視窗的方法
        //EXIT_ON_CLOSE是關閉整個程序,DISPOSE_ON_CLOSE只關閉當前介面,DO_NOTHING_ON_CLOSE不能關閉
        //然後是按鍵,↓,e.getKeyCode() == 40,這裡的40指的就是↓鍵
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //首先,在服務端摁↓,直接,改變線的狀態,0搖擺變為1放線,礦工狀態1手在上變為2手在下
                //這裡摁完後,狀態改變了
                if (publicMeans.isServer && Line.state1 == 0 && e.getKeyCode() == 40) {Line.state1 = 1; Miner.state1 = 2;}
                //看客戶端,在客戶端摁↓,客戶端向服務端傳送"keys:40;"
                if (!publicMeans.isServer && publicMeans.state_line2 == 0 && e.getKeyCode() == 40) {
                    Client.Send("keys:" + 40 + ";");//這裡是客戶端,要根據publicMeans裡線2的狀態判斷
                }
            }
        });

        if (publicMeans.isServer){timer();}
        //timer();//呼叫剛才寫的那個方法
        CreateGold();//創造金子 因為把創造金子的程式碼放進這個方法裡面了,所以需要呼叫執行

        String message = "";
        while (true){//先看這裡,這裡是個死迴圈,代表一直在重繪
            //在死迴圈裡判斷isEnd是否為true
            if (publicMeans.isEnd){
                //如果為true,則表示遊戲結束
                publicMeans.isEnd = false;//重置一下這個變數,防止再次開始遊戲時有bug
                //跳出之前需要判斷一下贏家+重新整理資料
                JudgeWinner();//判斷贏家
                IniData();//重新整理資料
                Close();//關閉介面
                break;//跳出迴圈
            }

            if (publicMeans.isServer){
                //這裡是服務端  我這裡寫了兩套變數
                //這一行,判斷剛才那個變數是否為40,若是,就代表客戶端摁了↓,改變礦工2的狀態和線的狀態,然後把這個變數置為0
                //因為如果不置為0,這個迴圈會一直進來,礦工2和線的狀態一直是這樣,所以要置為0
                if (publicMeans.clientKey == 40){Line.state2 = 1;Miner.state2 = 2;publicMeans.clientKey = 0;}
                repaint();//重繪
                UpdateThis();//更新第一套
                //下面的if就是判斷第一套和第二套有什麼不一樣
                //有不一樣的就放進message裡面,並且加上說明 比如上次是1,這次是2
                //比如第一個,礦工1的狀態不一樣了,就把 "miner1:2;" 這一段新增到message中
                //資料格式"miner1:0;miner2:0;" 注意這個分號
                if (state_miner1 != Last_state_miner1){message += "miner1:" + state_miner1 + ";";}
                if (state_miner2 != Last_state_miner2){message += "miner2:" + state_miner2 + ";";}
                if (state_line1 != Last_state_line1 || endX1 != Last_endX1 || endY1 != Last_endY1 || length1 != Last_length1 || dir1 != Last_dir1){
                    message += "line1:" + Line.state1 + " " + Line.endX1 + " " + Line.endY1 + " " + Line.angle1 + " " + Line.length1 + " " + Line.dir1 + ";";
                }
                if (state_line2 != Last_state_line2 || endX2 != Last_endX2 || endY2 != Last_endY2 || length2 != Last_length2 || dir2 != Last_dir2){
                    message += "line2:" + Line.state2 + " " + Line.endX2 + " " + Line.endY2 + " " + Line.angle2 + " " + Line.length2 + " " + Line.dir2 + ";";
                }
                if (money1 != Last_money1){message += "money1:" + money1 + ";";}
                if (money2 != Last_money2){message += "money2:" + money2 + ";";}
                //現在準備給客戶端傳值,傳倒數計時
                if (timeSs != Last_timeSs){message += "time:" + timeSs + ";";}//現在已經把倒數計時新增到message裡面了
                //看這裡,這裡用isFirst,是為了讓if裡面的語句走一次
                //因為首先要把objectList裡面的金子都發給客戶端,讓客戶端去畫
                //然後不能每次都傳送所有的,因為金子不是一直都在變化的,只有被抓取後金子才會改變
                if (isFirst){
                    for (Object obj:objectList){
                        message += "firstGold:" + obj.x + " " + obj.y + " " + obj.width + ";";
                    }
                    isFirst = false;
                }
                for (int i = 0; i < Last_gold.size(); i++){
                    //這裡本來是應該走被註釋掉的程式碼,但是還不知道為什麼有bug,就是不會進入if語句
                    //現在也能正常執行
                    message += "gold:" + i + " " + gold.get(i).x + " " + gold.get(i).y + " " + gold.get(i).flag + ";";
//                    if (gold.get(i).x != Last_gold.get(i).x || gold.get(i).y != Last_gold.get(i).y || gold.get(i).flag != Last_gold.get(i).flag){
//                        message += "gold:" + i + " " + gold.get(i).x + " " + gold.get(i).y + " " + gold.get(i).flag + ";";
//                        System.out.println("gold:" + i + " " + gold.get(i).x + " " + gold.get(i).y + " " + gold.get(i).flag);
//                    }
                }
                //以上就是把改變的資料放在message中
                Server.Send(message);//這個就是剛才寫的“傳送”  這裡一起傳送 然後客戶端去解析
                message = "";
                UpdateLast();//執行完1遍後更新第二套
                //為什麼?因為要根據這兩套變數判斷有哪些資料發生變化了,如果發生變化了,就把它們放進一個字串裡,最後傳送給客戶端
                //客戶端讀取並解析這些變數,並展示到客戶端的介面上
                //所以說服務端的資料和客戶端的資料是兩套資料
                //服務端需要一直把變化的資料發給客戶端

                //這裡是在一直迴圈的,我們可以在這裡實時檢測金子有沒有被抓完
                //如果金子的flag=3,代表金子被抓完丟掉了
                if (!isFirst){
                    for (Object obj:objectList){
                        if (obj.flag == 3){num++;}
                    }
                    if (num == objectList.size()){
                        //如果num=集合裡的金子數量,表示所有的金子都被抓住扔出了
                        Server.Send("遊戲結束");//告訴客戶端遊戲結束了 去解析一下
                        publicMeans.isEnd = true;
                        timer.cancel();//關閉倒數計時的那個定時器
                    }
                    num = 0;//重置一下num,要不num會一直加
                }
            }
            else {
                repaint();//重繪
            }

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

(4)建立執行緒,用於開啟遊戲介面(OpenThread.java)

public class OpenThread extends Thread{//在準備好進入遊戲介面時,開啟這個執行緒
    public GameFrame gameFrame;
    @Override
    public void run() {
        gameFrame = new GameFrame();
        gameFrame.launch();
    }
}

4.在publicMeans.java中建立所需變數

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class publicMeans {
    public static boolean isServer = false;//記錄此程式是服務端還是客戶端,服務端:true;客戶端:false
    public static boolean isConn = false;//記錄是否連線成功
    public static boolean isEnd = false;//表示遊戲是否結束
    private static int screenW = (int)Toolkit.getDefaultToolkit().getScreenSize().width;
    private static int screenH = (int)Toolkit.getDefaultToolkit().getScreenSize().height;
//    private static GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
//    private static AffineTransform tx = gc.getDefaultTransform();
//    public static double uiScaleX = tx.getScaleX();
//    public static double uiScaleY = tx.getScaleY();

    public static int GameWinW = 985;
    public static int GameWinH = 719;

    public static int ChatW = 375;
    public static int ChatH = 719;

    //聊天視窗在左,遊戲視窗在右
    public static int ChatX = screenW/2-(GameWinW+ChatW)/2;
    public static int ChatY = screenH/2-ChatH/2;
    public static int GameWinX = ChatX + ChatW;
    public static int GameWinY = ChatY;

    //遊戲視窗在左,聊天視窗在右
//    public static int GameWinX = screenW/2-(GameWinW+ChatW)/2;
//    public static int GameWinY = screenH/2-GameWinH/2;
//    public static int ChatX = GameWinX + GameWinW;
//    public static int ChatY = GameWinY;

    public static String ip = null;
    public static int port = 0;
    //以下就是客戶端所需要的資料,客戶端根據以下資料進行畫面重繪
    public static int state_miner1 = 0, state_miner2 = 0;
    public static int state_line1 = 0, endX1 = 0, endY1 = 0, dir1 = -1;
    public static int state_line2 = 0, endX2 = 0, endY2 = 0, dir2 = 1;
    public static double angle1 = 0.9, length1 = 50;
    public static double angle2 = 0.1, length2 = 50;
    //public static Object gold = new Object();
    public static List<Object> gold = new ArrayList<>();
    public static int money1 = 0, money2 = 0;
    public static int timeSs = -1;//這個用來記錄倒數計時,是客戶端引用的
    public static int clientKey = 0;
}

5.分別在服務端和客戶端中呼叫開啟遊戲介面的執行緒+訊息解析

①在服務端和客戶端雙方連線成功後,“開始遊戲”的按鈕才會能被點選,服務端點選按鈕後,會向客戶端傳送“開始遊戲”,告訴客戶端可以開啟遊戲介面一起遊戲了;所以客戶端需要在解析服務端訊息時呼叫執行緒;
②客戶端按“↓”時,需要告訴服務端我放鉤子了,解析的時候需要注意。

服務端的“開始遊戲”按鈕(Server.java):

        btBegin.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {//點選按鈕會執行這個裡面
                if (publicMeans.isConn){//如果連線成功,點選開始遊戲才可以進入遊戲介面
                    Send("開始遊戲");//給客戶端傳送”開始遊戲“
                    Server.textArea.append("開始遊戲。\r\n");
                    btBegin.setEnabled(false);//這裡是開始遊戲按鈕不能點選,因為已經開始遊戲了
                    OpenThread openThread = new OpenThread();
                    openThread.start();
                }
            }
        });

服務端解析(ServerThread.java):

    private static String[] message = null;
    public static void Analysis(){//解析客戶端發來的資訊
        message = info.split(";");
        for (int i = 0; i < message.length; i++){
            if (message[i].startsWith("keys:")){
                publicMeans.clientKey = 40;//然後把這個變數改變
            }
        }
    }
//這裡是ServerThread.java中的run()裡面的while(true)
while(true){//這裡使用死迴圈,是因為要一直監視客戶端有沒有發過來資訊
                while((info = br.readLine()) != null){//剛才的"keys:40;"就來這裡了
                    //br.readLine()就是讀取客戶端發過來的資訊,br.readLine()讀取一行,然後使用while((info = br.readLine()) != null)
                    //讀取發過來的所有資訊
                    Analysis();//解析客戶端發過來的資訊
                }
            }

客戶端解析(ClientThread.java):

    @Override
    public void run() {//執行緒開始,自動執行
        try{
            socket = new Socket(publicMeans.ip,publicMeans.port);//服務端的ip地址和埠號 這裡的ip和port也就是剛才文字框裡輸入的
            publicMeans.isServer = false;//如果成功進入到這一步,則代表此視窗是客戶端視窗
            publicMeans.isConn = true;//連線成功
            Client.textArea.append("連線成功。\r\n");//"\r\n"的意思是換行
            Client.btConn.setEnabled(false);//禁用客戶端的連線按鈕

            os = socket.getOutputStream();
            pw = new PrintWriter(os);

            is = socket.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);//這裡就是客戶端接收的地方
            while(true){//這裡使用死迴圈,是因為要一直監視服務端有沒有發過來資訊
                //剛才在服務端裡面,開始遊戲的時候服務端向客戶端傳送了“開始遊戲”,這裡解析
                while((info = br.readLine()) != null){
                    if (info.startsWith("開始遊戲")){
                        Client.textArea.append("開始遊戲。\r\n");
                        openThread = new OpenThread();
                        openThread.start();//這個OpenThread就是剛才新建的執行緒,當客戶端收到開始遊戲時,進入到遊戲介面
                    } else if (info.startsWith("遊戲結束")) {
                        publicMeans.isEnd = true;
                        System.out.println(3);
                        openThread.gameFrame.Close();//呼叫執行緒裡的gameFrame,gameFrame是遊戲介面,呼叫它的Close方法,關閉介面
                    } else {
                        Analysis();//解析服務端發過來的內容
                    }
                }
            }//跟服務端一個意思
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
    private static String[] message = null;
    private static String[] messageTemp = null;
    private static Object obj;
    public static void Analysis(){//"miner1:0;miner2:0;"
        message = info.split(";");//首先以分號為間隔分組,放到message陣列中 miner1:0 miner2:0
        for (int i = 0; i < message.length; i++){//然後迴圈message陣列 此時message[0]=miner1:0 message[1]=miner2:0
            //進入判斷
            //比如第一個if 判斷message[0]是不是以"miner1"開頭的,miner1:0,是,就進入,把publicMeans裡的變數置為這個接收到的資料
            //因為接收的資料是string格式的,所以使用Integer.parseInt轉為int格式
            if (message[i].startsWith("miner1:")) {publicMeans.state_miner1 = Integer.parseInt(message[i].substring(7));}//miner1:0
            else if (message[i].startsWith("miner2:")){publicMeans.state_miner2 = Integer.parseInt(message[i].substring(7));}
            else if (message[i].startsWith("line1:")){
                messageTemp = message[i].substring(6).split(" ");
                publicMeans.state_line1 = Integer.parseInt(messageTemp[0]);
                publicMeans.endX1 = Integer.parseInt(messageTemp[1]);
                publicMeans.endY1 = Integer.parseInt(messageTemp[2]);
                //這裡需要的資料angle1是double格式的,所以使用Double.parseDouble把string轉為double
                publicMeans.angle1 = Double.parseDouble(messageTemp[3]);
                publicMeans.length1 = Double.parseDouble(messageTemp[4]);
                publicMeans.dir1 = Integer.parseInt(messageTemp[5]);
            }
            else if (message[i].startsWith("line2:")){
                messageTemp = message[i].substring(6).split(" ");
                publicMeans.state_line2 = Integer.parseInt(messageTemp[0]);
                publicMeans.endX2 = Integer.parseInt(messageTemp[1]);
                publicMeans.endY2 = Integer.parseInt(messageTemp[2]);
                publicMeans.angle2 = Double.parseDouble(messageTemp[3]);
                publicMeans.length2 = Double.parseDouble(messageTemp[4]);
                publicMeans.dir2 = Integer.parseInt(messageTemp[5]);
            }
            else if (message[i].startsWith("money1:")){publicMeans.money1 = Integer.parseInt(message[i].substring(7));}
            else if (message[i].startsWith("money2:")){publicMeans.money2 = Integer.parseInt(message[i].substring(7));}
            else if (message[i].startsWith("gold:")){
                messageTemp = message[i].substring(5).split(" ");
                if (publicMeans.gold.size() == 0){continue;}
                publicMeans.gold.get(Integer.parseInt(messageTemp[0])).x = Integer.parseInt(messageTemp[1]);
                publicMeans.gold.get(Integer.parseInt(messageTemp[0])).y = Integer.parseInt(messageTemp[2]);
                publicMeans.gold.get(Integer.parseInt(messageTemp[0])).flag = Integer.parseInt(messageTemp[3]);
            }
            else if (message[i].startsWith("time:")){//傳過來的是"time:8" 我們只需要把8賦值給publicMeans.timeSs
                publicMeans.timeSs = Integer.parseInt(message[i].substring(5));
                //這裡的substring就是從第五位開始擷取 第0位t 1=i 2=m 3=e 4=: 5=8 所以括號裡面寫5
            }
            else if (message[i].startsWith("firstGold:")){
                messageTemp = message[i].substring(10).split(" ");
                switch (messageTemp[2]){
                    case "133":
                        obj = new BigGold();
                        break;
                    case "72":
                        obj = new MiddleGold();
                        break;
                    case "24":
                        obj = new SmallGold();
                        break;
                    default:break;
                }
                obj.x = Integer.parseInt(messageTemp[0]);
                obj.y = Integer.parseInt(messageTemp[1]);
                publicMeans.gold.add(obj);
            }
            else {}
        }
    }

6.結束

相關文章