項目詳解 下


寫在篇前:在第一篇中所有項目源碼已經公開,本篇主要講解下:

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,系統適配性問題,軟件適配性並沒有做很多測試,僅僅使用了幾個模擬器以及自己的兩款手機。所以適配性以及測試上還存在一些問題可能在某些機型上會出現奔潰現象。


四,總結

正如上一篇中所說,本軟件僅僅是一個鍛鍊式的軟件,目前源碼公開,希望或多或少有些許幫助。。

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