<五子棋局域网对战>项目详解 下


写在篇前:在第一篇中所有项目源码已经公开,本篇主要讲解下:

1,代码结构——方便了解整个项目

2,部分源码解释——方便有心人的阅读

3,系统存在的不足——改进


一,系统代码结构



1,包com.wxh.netgobang中分别是四个Activity界面

      MainActivity: 游戏主界面

      PmActivity: 人机对战界面

      PpPrepareActivity: 人人对战准备界面

      PpActivity:人人对战界面

       

2,包com.wxh.netgobang.pm中所有的代码都是人机对战源码,该源码沿用自以前本人下的一个单机五子棋代码, 所有代码直接挪用过来,所以没有具体整理就直接丢在一个包里面了。 其中五子棋AI部分也是从网上的分享处获得,有兴趣的可以研究一下...

           

3,包com.wxh.netgongba.chess,主要是人人对战界面中用到的一些类定义,例如:玩家、棋盘、棋盘线条、棋子座标等对象的定义

           

4,包com.wxh.netgongba.event, 主要是系统基于事件模型的一些定义:例如通讯监听、通讯适配器、主机信息、棋盘监听等事件类型定义。

           

5,包com.wxh.netgongba.inter 该包基本没有用到,系统设计之初整体结构大开大合,但是写的过程中又觉得没必要,所有有些接口定义并没有用到,或者用到了也没有发挥出其本来功效,可以忽略掉。

6,包com.wxh.netgongba.util 该包比较重要定义了系统的通讯类,以及系统通讯协议。

            


二 ,部分源码解释

本人认为系统主要的技术点在于:通讯、业务逻辑处理

1,通讯

当然在实现通讯之初首先需要做的是指定好通讯协议,不管你是用什么技术来实现,玩家之间沟通的语音协定必须双方协议好。这个协议的设定开放性较大,此处不做鳌诉。以下是系统中所用到的协议帧结构:所有数据的解包封包以及协议类型定义都在类com.wxh.netgongba.util.ProtocolUtil之中。



本系统采用的是UDP通讯方式。设计之初本打算是在主机搜索上采用UDP广播方式获取主机信息,而在玩家对弈过程中采用TCP/IP通讯方式的。但是在编写代码的过程中感觉UDP通讯更加简单一些,并且就采用一种通讯方式看上去更加整洁所以也就通篇采用了UDP。

这样一来通讯实现就很简单了。首先需要初始化一个UDP服务器,目的是用与接收:(1)主机信息询问请求(2)玩家对弈过程中信息共享。

 /**
* 初始化 广播服务
* 监听所有广播信息信息
*/
private void initServer(){
new Thread(new Runnable() {
@SuppressWarnings("resource")
@Override
public void run() {
DatagramSocket socket = null;
try {
socket = new DatagramSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
byte[] buf = new byte[64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while(true){
try {
socket.receive(packet);
byte[] data = Arrays.copyOfRange(buf, 0, packet.getLength());
byte[] de = ProtocolUtil.decode(data); 
if(de != null){
byte requestType = de[0];
if(requestType == ProtocolUtil.HOST_REQUEST){
//TODO 其他主机对我发出询问, 那么我需要发送我的信息给对方
if(isHost){
System.out.println("汇报本主机:"+deviceName);
responseHost(packet.getAddress().getHostAddress());
}
}else if(requestType == ProtocolUtil.HOST_RESPONSE){
//TODO 其他主机汇报了身份  那么我需要将身份信息保存下来
String ip = packet.getAddress().getHostAddress();
String name = new String(Arrays.copyOfRange(de, 1, de.length),"utf-8");
System.out.println("@@@获取主机:"+name+"***"+ip);
Host host = new Host(ip, name);
if(isNewHost(host)){
notifyNewHost(host);  //通知新主机信息
}
}else if(requestType == ProtocolUtil.HOST_STOP){
//TODO 其他主机通知我 他的主机已经撤销  那么我需要从列表中移除该主机
String ip = packet.getAddress().getHostAddress();
String name = new String(Arrays.copyOfRange(de, 1, de.length),"utf-8");
System.out.println("撤销主机:"+name);
Host host = new Host(ip, name);
host = removeHost(host);
if(host != null ){
notifyRemoveHost(host);  //通知移除主机
}
}else if(requestType == ProtocolUtil.HOST_IN_REQUEST){
//TODO  其他主机请求加入主机
String ip = packet.getAddress().getHostAddress();
if(!isHost){
send(ip, ProtocolUtil.hostInResponse(ProtocolUtil.FAIL));
continue;
}
String name = new String(Arrays.copyOfRange(de, 1, de.length),"utf-8");
System.out.println("主机:"+name+"请求加入游戏");
Host host = new Host(ip, name);
if(host != null ){
notifyEnemyRequestCome(host);   //对手请求加入游戏
}
}else if(requestType == ProtocolUtil.HOST_IN_RESPONSE){
//TODO  请求加入主机的反馈
byte r = de[1];
if(r == ProtocolUtil.SUCCESS){
//当前是客户端模式
Host host = new Host();
host.setIp(packet.getAddress().getHostAddress());
host.setName(deviceName);
setEnemyHost(host);
notifyGameIn(true);    //通知请求加入游戏结果
}else
notifyGameIn(false);
}else if(requestType == ProtocolUtil.GAME_READY){
//TODO  对手已经准备游戏
byte r = de[1];
notifyEnemyGameReady((r == (byte)0x01)?true:false);  //通知对手准备情况
}else if(requestType == ProtocolUtil.GAME_EXIT){
//TODO 对手退出游戏
notifyEnemyGameExit();     
}else if(requestType == ProtocolUtil.GAME_POSITION){
//TODO 对手落子位置
System.out.println("对手落子位置:("+de[1]+","+de[2]+")");
notifyEnemyGamePosition(de[1], de[2]);
}}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}

当玩家需要搜索居于网内主机信息时,需要广播功能:

/**
* 局域网广播
* @param bag   广播数据
* @return    true/false
*/
private boolean broadCast(final byte[] bag){
new Thread(new Runnable() {
public void run() {
try{
@SuppressWarnings("resource")
DatagramSocket socket = new DatagramSocket();
InetAddress address = InetAddress.getByName(broadcast);
socket.setBroadcast(true);
// socket.joinGroup(address);
DatagramPacket packet = new DatagramPacket(bag, bag.length);
packet.setAddress(address);
packet.setPort(port);
socket.send(packet);
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
return true;
}

当玩家请求加入游戏、通知游戏准备信息、通知落子信息、通知离开游戏等信息的时候,需要发送通信数据包:

/**
* 向特定的主机发送 数据
* @param ip
* @param bag
* @return
*/
private boolean send(final String ip,final byte[] bag){
new Thread(new Runnable() {
public void run() {
try{
@SuppressWarnings("resource")
DatagramSocket socket = new DatagramSocket();
InetAddress address = InetAddress.getByName(ip);

DatagramPacket packet = new DatagramPacket(bag, bag.length);
packet.setAddress(address);
packet.setPort(port);
socket.send(packet);
}catch(Exception e){
e.printStackTrace();
}

}
}).start();
return true;
}


2,业务逻辑处理

业务处理部分并没有太大的技术含量就是稍微复杂一点,只是要理清楚敌我双方关系。当双方玩家都进入对战的时候,需要区分双方玩家是主机身份(我建立的主机)还是客机身份(对方建立的主机)(系统默认主机身份玩家执黑子先行,客机玩家执白子)。因为不同身份代码处理过程中会有所区别。

例如:

   当双方在游戏过程中,对方玩家退出游戏了,如果你是主机身份接收到对方退出的消息,那么你只需要将游戏重新初始化,将对手玩家信息清空等待下一个对手进入游戏。如果你是客机身份,那么也就是说主机信息不存在了,那么游戏也就退出了,客机也将被迫退出游戏。

等等类似...


三,系统存在的不足

1,界面并不是很美观,这是个程序猿硬伤。

2,系统的通讯采用UDP,这也就表明一旦在通讯过程中丢包了,那就完蛋了。 其实这个问题也好解决就像QQ消息通讯也有是采用的UDP,但是它同时也在应用层上对每个UDP包进行了反馈确认,所以不至于丢包。很显然本系统中并没有做反馈确认处理。这已处理只需要添加响应的反馈协议以及多一些代码处理即可!

3,系统适配性问题,软件适配性并没有做很多测试,仅仅使用了几个模拟器以及自己的两款手机。所以适配性以及测试上还存在一些问题可能在某些机型上会出现奔溃现象。


四,总结

正如上一篇中所说,本软件仅仅是一个锻炼式的软件,目前源码公开,希望或多或少有些许帮助。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章