10

使用Java语言编写一个五子棋UI界面并实现网络对战功能(非局域网)

 3 years ago
source link: http://www.cnblogs.com/AIERSTOM/p/Aierstom_11.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用Java语言编写一个五子棋UI界面并实现网络对战功能(非局域网)

一,前期准备

1,Java IDE(Eclipse)与JDK的安装与配置

jdk-15.0.1-免配置路径版

提取码:earu

免安装版Eclipse 解压即可使用

提取码:5iyy

网络上很多配置jdk的方法,我不再重复
这里提供一种便捷操作的方法(针对新手)
由于高版本jdk不需要手动配置路径,将我上传的jdk资源下载后一键安装,路径即可自动配置

2,一台云主机

阿里云,腾讯云,华为云的云主机均可,我用的是windows系统
(window是自带的远程连接很方便),如果想用其他的也可,最好选择一个有桌面的,这样调试起来容易些
在云主机上同样需要安装Eclipse与配置jdk,步骤同上
如果内存较大的可以安装数据库,这样编写的程序上可以加账号登录注册功能

我的云主机

7b6VBr2.png!mobile

3,另一台可供测试可以联网的电脑或虚拟机

建议方便的同学用另一台电脑,一台电脑用手机热点,另一台用WiFi
这样可以测试外网的连接情况

4,转换Java Jar为exe文件的软件(如exe4j)

网上很多关于转换的教程(非必须,如果不需要可以忽略这一步)

二,功能分析与效果展示

1,这个程序主要分为三部分,UI界面,单机落子部分,联网落子部分,而UI界面又分为登录界面和棋盘界面。在这篇文章中UI界面与联网落子部分为讲述重点。

2,登录界面实现的功能有以下几点,首先当启动程序时,应自动检测与服务器的连接,如果连接失败,则不出现网络登录入口,如果连接成功,则出现网络对战登录入口。

连接失败效果展示

R3yquqF.png!mobile7vqUn2B.png!mobile

连接成功效果展示

qEFJRf2.png!mobilevyIzIzR.png!mobile

3,棋盘界面应满足的功能,黑白棋的落子,判断胜利,重新开始

棋盘效果展示

7RVJru.png!mobile

4,网络对战应满足的功能,由于很多电脑使用路由器与外网访问(有的通信服务提供商会隐藏真实ip,故两台由不同路由器连接的电脑很难建立连接),同时增加编写难度,采用下棋双方与服务器连接的方式,A->服务器<-B,A<-服务器->B,程序应做到迅速响应服务器信息,减少延迟,双方棋盘信息应一致。

I3M3QnB.png!mobile

三,具体实现方法

1,棋盘UI的实现

JPanel jpan1 = new JPanel() {                     //根据新棋盘信息作图,覆盖原有Panel
 	        		private static final long serialVersionUID = 1L;
 	        		public void paint(Graphics graphics){         //重构paint函数
 	        			int xst=20,yst=20,add=32;
 	                    for(int t=0;t<15;t++)                      //画竖线
 	                    {
 	                    	graphics.drawLine(xst,yst,xst,468);
 	                    	xst=xst+add;
 	                    }
 	                    xst=20;yst=20;add=32;
 	                    for(int t=0;t<15;t++)                      //画横线
 	                    {
 	                    	graphics.drawLine(xst,yst,468,yst);
 	                    	yst=yst+add;
 	                    }
 	                  
 	    			   graphics.setColor(Color.BLACK);             //画棋盘上五个黑点
 	                   graphics.fillOval(113, 113, 6, 6);
 	                   graphics.fillOval(369, 113, 6, 6);
 	                   graphics.fillOval(113, 369, 6, 6);
 	                   graphics.fillOval(369, 369, 6, 6);
 	                   graphics.fillOval(241, 241, 6, 6);
 	                   
 	                   for(int t=0;t<15;t++)                       //根据棋盘数组里存储的棋子信息画黑白子
 	                   {
 	                	   for(int t1=0;t1<15;t1++)
 	                	   {
 	                		   if(node[t][t1]==1)
 	                		   {
 	                			   graphics.setColor(Color.BLACK);
 	                               graphics.fillOval(t1*32+20-13,t*32+20-13,26,26);
 	                		   }
 	                		   if(node[t][t1]==-1)
 	                		   {
 	                			   graphics.setColor(Color.WHITE);
 	                               graphics.fillOval(t1*32+20-13,t*32+20-13,26,26);
 	                		   }
 	                	   }
 	                   }
 	        	    }
 	        		}

由于每次落子棋盘都会发生变化,所以设置一个鼠标触发事件,当每次触发都将窗口重绘,根据棋盘信息数组里的内容更新到当前局面。

2,网络对战(服务器端编程)

网络对战的实质是socket编程,即客户端A将落子信息传给服务器,服务器将信息传给客户端B,接着客户端B将落子信息传给服务器,服务器传给客户端A,故在服务器端编程中应监听两个端口(我设置的是1075和1056)客户端A将信息通过1075端口传给服务器,服务器将A传过来的信息通过1056传给服务器B,默认先连接的是黑子,当黑子连接成功后,监听白子连接。

package cilent;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class test {
    public static void main(String[] args) {
        ServerSocket server,server1;
        try {
            server = new ServerSocket(1075);
            Socket socket=server.accept();
            System.out.println("black is ok");
            server1 = new ServerSocket(1056);
            Socket socket1=server1.accept();
            System.out.println("white is ok");
            while(true){
                System.out.println("----------");
                InputStream in,in1;
                try {
                    in = socket.getInputStream();
                    byte [] b=new byte[1024];
                    StringBuffer sb=new StringBuffer();
                    String s;
                    if(in.read(b) !=-1){
                        s=new String(b);
                        sb.append(s);
                    }
                    OutputStream out1=socket1.getOutputStream();
                    System.out.println("黑发给白"+sb);
                    out1.write(sb.toString().getBytes());
                    out1.flush();
                    in1 = socket1.getInputStream();
                    byte [] b1=new byte[1024];
                    StringBuffer sb1=new StringBuffer();
                    String s1;
                    if(in1.read(b1) !=-1){
                        s1=new String(b1);
                        sb1.append(s1);
                    }
                    OutputStream out=socket.getOutputStream();
                    System.out.println("白发给黑"+sb1);
                    out.write(sb1.toString().getBytes());
                    out.flush();
                } catch (IOException e) {
                   // e.printStackTrace();
                }
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

3,网络对战(客户端编程)

在客户端这边不仅要考虑数据的发送与接收,还要考虑接收或发送的数据在窗体上如何实时的显示,为此我自己创立了一套编码解码方式,为方便每次发送的信息的格式为XX*YY,前两位为二维数组行数,后两位为二维数组列数,发送部分代码如下

if(xrec<=9)                        //确认发送数据格式 XX*XX XX指二维数组列,行
            		   sent="0"+xrec;
            	   else
            		   sent=""+xrec;
            	   sent=sent+"*";
            	   if(yrec<=9)
            		   sent=sent+"0"+yrec;
            	   else
            		   sent=sent+""+yrec;
            	   System.out.println("==========");
                   try {
					socket.getOutputStream().write(sent.getBytes());
					System.out.println("MY sent:"+sent);
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
                   try {
					socket.getOutputStream().flush();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}      //发送完毕

由于socket的接收函数有阻塞性,当执行接收函数时,程序被阻塞,窗体无法及时更新,这样就会出现无法更新落子信息,当接收到对方落子时一次更新两个棋子的情况,为解决这个问题,将本机落子与接收落子分隔开,当鼠标按下时更新本机落子,当鼠标松开时接收服务器信息。

void jieshou(Socket socket, JFrame jFrame)
	{
		 
        //temp=1;
        InputStream in = null;
		try {
			in = socket.getInputStream();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			JOptionPane.showMessageDialog(null,"对方未就绪!");
		}
        byte[] b = new byte[1024];
        StringBuffer sb = new StringBuffer();
       
        try {
			if (in.read(b) != -1) {
			       s = new String(b);
			       System.out.println(s);
			       sb.append(s);
			   }
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			 JOptionPane.showMessageDialog(null,"对方未就绪!");
		}
        System.out.println("来自服务器的数据:" + sb);  //收到对方落子信息
        int xnew=(sb.charAt(0)-'0')*10+(sb.charAt(1)-'0');//解码
        int ynew=(sb.charAt(3)-'0')*10+(sb.charAt(4)-'0');
        if(node[xnew][ynew]==0) {              //更改对方落子
            node[xnew][ynew]=-1;
           // m=1;
            }
           
            JPanel jpan1 = new JPanel() {                     //根据新棋盘信息作图,覆盖原有Panel
        		private static final long serialVersionUID = 1L;
        		public void paint(Graphics graphics){
        			super.paint(graphics);
        			int xst=20,yst=20,add=32;
                    for(int t=0;t<15;t++)
                    {
                    	graphics.drawLine(xst,yst,xst,468);
                    	xst=xst+add;
                    }
                    xst=20;yst=20;add=32;
                    for(int t=0;t<15;t++)
                    {
                    	graphics.drawLine(xst,yst,468,yst);
                    	yst=yst+add;
                    }
                  
    			   graphics.setColor(Color.BLACK);
                   graphics.fillOval(113, 113, 6, 6);
                   graphics.fillOval(369, 113, 6, 6);
                   graphics.fillOval(113, 369, 6, 6);
                   graphics.fillOval(369, 369, 6, 6);
                   graphics.fillOval(241, 241, 6, 6);
                   
                   for(int t=0;t<15;t++)
                   {
                	   for(int t1=0;t1<15;t1++)
                	   {
                		   if(node[t][t1]==1)
                		   {
                			   graphics.setColor(Color.BLACK);
                               graphics.fillOval(t1*32+20-13,t*32+20-13,26,26);
                		   }
                		   if(node[t][t1]==-1)
                		   {
                			   graphics.setColor(Color.WHITE);
                               graphics.fillOval(t1*32+20-13,t*32+20-13,26,26);
                		   }
                	   }
                   }
        	    }
        		};
        		jFrame.add(b1);
        	jFrame.add(jpan1);	
        	jFrame.setVisible(true);
        	//temp=0;
	}

如果有兴趣的同学也可以在服务器端加一个本地服务器(推荐SQL server)搭配jdbc实现一个客户端登录程序(类似QQ),具体如何实现我会在下节详细叙述。

有需要的可以给我留言,分享源码


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK