目录
编辑器:IDEA
语言:Java
主要技术:Socket网络通讯
具体的可查看
Java-黄金矿工(一) https://www.cnblogs.com/xbxxx/p/18207095
Java-黄金矿工(二) https://www.cnblogs.com/xbxxx/p/18207183
效果
https://www.bilibili.com/video/BV1Um4y187FV/?spm_id_from=333.999.0.0
主要思想
①服务端与客户端通讯,互相发送各个状态,以达到相对同步;
②共3个界面:服务端、客户端、游戏界面;
③倒计时结束,根据双方的金钱进行判断输赢。
实现
1.矿工(Miner.java)
首先在publicmain中新建变量:state_miner1和state_miner2,分别表示矿工1和矿工2的状态:
public static int state_miner1 = 0, state_miner2 = 0;
然后新建Miner.java:
点击查看代码
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);
}
}
}
}
2.金块(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.矿工的线(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);
}
}
4.粉色小球(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);
}
}
5.背景(Bg.java)
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);
}
}
}
6.游戏界面(GameFrame.java)
点击查看代码
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class GameFrame extends JFrame {//新建界面
//这个画面是用paint画的
Bg bg = new Bg();//背景类
Miner miner = new Miner();//矿工
Line line = new Line(this);//线,这个东西比较多 end
Image canvasImage;
BigGold bigGold;//大金块
MiddleGold middleGold;//中等金块
SmallGold smallGold;//小金子
boolean isOverlap = false;
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;}
}
//这里的中金子和小金子一样,和大金子一样
}
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);
}
}
}
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");//打印到客户端的文本框中
}
}
//刷新数据,如果不刷新数据,再次点击开始游戏时,画面会出现问题
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;
}
//新建一个关闭窗口的方法,需要的时候直接调用就可以
public void Close(){
this.setVisible(false);
this.dispose();
Server.btBegin.setEnabled(true);
}
//现在判断游戏是否结束 :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次
}
@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);}
}
}
7.Server.java
点击查看代码
import java.io.*;
import java.net.Socket;
public class ServerThread extends Thread{//extends Thread创建服务端线程
private Socket socket = null;
//public ServerThread(Socket socket){this.socket = socket;}
//接收来自客户端信息的相关变量
public InputStream is = null;
public InputStreamReader isr = null;
public static BufferedReader br = null;
public static String info = null;
//向客户端发送信息的相关变量
public OutputStream os = null;
public PrintWriter pw = null;
@Override
public void run() {//刚才的线程开启后,自动执行run
try{
socket = Server.serverSocket.accept();//等待客户端连接,若没有连接,就会一直在这一行代码等着,若连接了,就会下一步
publicMeans.isServer = true;//表示这个窗口是服务端
publicMeans.isConn = true;//这里表示客户端连接了,所以把isConn置为true
Server.textArea.append("连接成功。\r\n");//把这句话添加到textArea中
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){//刚才的"keys:40;"就来这里了
//br.readLine()就是读取客户端发过来的信息,br.readLine()读取一行,然后使用while((info = br.readLine()) != null)
//读取发过来的所有信息
Analysis();//解析客户端发过来的信息
}
}
}catch (Exception e){}
}
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;//然后把这个变量改变
}
}
}
}
8.ServerThread.java
点击查看代码
import java.io.*;
import java.net.Socket;
public class ServerThread extends Thread{//extends Thread创建服务端线程
private Socket socket = null;
//public ServerThread(Socket socket){this.socket = socket;}
//接收来自客户端信息的相关变量
public InputStream is = null;
public InputStreamReader isr = null;
public static BufferedReader br = null;
public static String info = null;
//向客户端发送信息的相关变量
public OutputStream os = null;
public PrintWriter pw = null;
@Override
public void run() {//刚才的线程开启后,自动执行run
try{
socket = Server.serverSocket.accept();//等待客户端连接,若没有连接,就会一直在这一行代码等着,若连接了,就会下一步
publicMeans.isServer = true;//表示这个窗口是服务端
publicMeans.isConn = true;//这里表示客户端连接了,所以把isConn置为true
Server.textArea.append("连接成功。\r\n");//把这句话添加到textArea中
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){//刚才的"keys:40;"就来这里了
//br.readLine()就是读取客户端发过来的信息,br.readLine()读取一行,然后使用while((info = br.readLine()) != null)
//读取发过来的所有信息
Analysis();//解析客户端发过来的信息
}
}
}catch (Exception e){}
}
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;//然后把这个变量改变
}
}
}
}
9.Client.java
点击查看代码
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.Socket;
public class Client extends JFrame {
public static JTextArea textArea = new JTextArea(5,10);
private static JScrollPane scrollPane;
private static JLabel labelIP = new JLabel("IP:");
private static JTextField textIP = new JTextField("127.0.0.1",10);
private static JLabel labelPort = new JLabel("端口号:");
private static JTextField textPort = new JTextField("8088",10);
public static JButton btConn = new JButton("连接");
void launch(){
this.setVisible(true);
this.setSize(500,500);
this.setLocationRelativeTo(null);//窗口位置:居中
this.setTitle("黄金矿工联机版(client)");
this.setResizable(false);
setLayout(null);//设置布局
setDefaultCloseOperation(EXIT_ON_CLOSE);
//textArea.setOpaque(false);//透明
textArea.setForeground(Color.black);
textArea.setBackground(Color.white);
textArea.setFont(new Font("幼圆",Font.BOLD,15));
textArea.setEnabled(false);//只读
textArea.setBounds(0,0,500,200);
scrollPane = new JScrollPane(textArea);
scrollPane.setBounds(0,0,500,200);
//"IP:"
labelIP.setFont(new Font("幼圆",Font.PLAIN,15));
labelIP.setBounds(145,250,100,20);
//输入IP地址的文本框
textIP.setFont(new Font("幼圆",Font.PLAIN,15));
textIP.setBounds(238,250,100,20);
//“端口:”
labelPort.setFont(new Font("幼圆",Font.PLAIN,15));
labelPort.setBounds(145,300,100,20);
//输入端口号的文本框
textPort.setFont(new Font("幼圆",Font.PLAIN,15));
textPort.setBounds(238,300,100,20);
//连接按钮
btConn.setFont(new Font("幼圆",Font.PLAIN,15));
btConn.setBounds(200,350,100,30);
btConn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//比如“ 123 ”,加了.trim就会得到“123”
publicMeans.ip = textIP.getText().trim();//获取输入的IP,就是textIP里面的 .trim()的意思是删除文本框里面内容前后的空格
publicMeans.port = Integer.parseInt(textPort.getText().trim());//因为port是int类型,所以要使用Integer.parseInt把string转为int
BuildClient();//点击连接按钮后,去创建客户端
}
});
this.add(scrollPane);
this.add(labelIP);
this.add(textIP);
this.add(labelPort);
this.add(textPort);
this.add(btConn);
}
public static void main(String[] args){
Client client = new Client();
client.launch();
}
private static Socket socket;
private static ClientThread thread;
public static void BuildClient(){//创建客户端
try{
thread = new ClientThread(socket);//跟刚才一样,创建一个客户端线程
thread.start();
}catch (Exception ignored){}
}
//客户发送信息时引用
public static void Send(String message){
thread.pw.write(message + "\r\n");
thread.pw.flush();
}
//关闭客户端时引用 还有这个,暂时还没有用,但是最好可以加上
//在关闭客户端时调用这个方法,主动把socket和thread相关变量关掉
public void ShutDown(){
try{
if(thread.is != null){thread.is.close();}
if(thread.isr != null){thread.isr.close();}
if(thread.br != null){thread.br.close();}
if(thread.os != null){thread.os.close();}
if(thread.pw != null){thread.pw.close();}
if(socket != null){socket.close();}
}catch (Exception ignored){}
}
}
10.ClientThread.java
点击查看代码
import java.io.*;
import java.net.Socket;
public class ClientThread extends Thread{//创建客户端线程
private Socket socket;
public ClientThread(Socket socket){this.socket = socket;}
//接收来自服务端信息的相关变量
public InputStream is = null;
public InputStreamReader isr = null;
public BufferedReader br = null;
private static String info = null, infoAll = null;
//向服务端发送信息的相关变量
public OutputStream os = null;
public PrintWriter pw = null;
OpenThread openThread;
@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 {}
}
}
}
11.OpenThread.java
public class OpenThread extends Thread{//在准备好进入游戏界面时,开启这个线程
public GameFrame gameFrame;
@Override
public void run() {
gameFrame = new GameFrame();
gameFrame.launch();
}
}
12.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;
}