我們這一期要來探討網路對戰方面的程式,在寫網路對戰時, 重要的已經不是遊戲勝負的規則判斷等,而是電腦與電腦間溝通傳遞資料的流程排定, 在作這種程式的時候,如果你能將情況分析得宜的話,其實其中的 流程不會比單機的困難太多(當然,我指的是這個程式而已,其它的我可不敢保證...), 舉個例子來說好了,猜數字遊戲如果要分類的話, 可以算作是回合制的遊戲,那麼,我們就要注意,當其中一名User在進行猜數字時, 另一名User的動作就應該受到限制,不然大家各猜各的,流程就會整個亂掉了, 網路的程式會發生的情況太多了,所以如果可以的話,將自己的程式流程化為一個流程圖 可以比較有效的幫助你更詳細的思考整個的程式流程。
在這裡要向各位再說明一下,這只是一個很簡單的連線遊戲,其實中間有很多東西我都故 意省略過去,嚴格來說,這種程式出錯的機率很大的,如果你真的有心要寫一個連線程式, 一定要更深入的去研究才可以的,舉個例子好了,在兩臺電腦連線之後,如果某一方斷線 的話,我會回傳一個斷線訊息給對方,讓對方知道斷線了,結束這一次的連線,但這其實 是非常被動而且不確實的做法,在兩臺電腦連線的時候,會發生的情況太多了,如果對方 當機了呢?你永遠也不會知道,當然,我們這只是一個連結兩臺電腦的小程式,斷線就斷 線,沒有什麼大不了的,但是,如果你今天寫的是個更大的Case呢?在正常的作法上,其實 兩臺電腦應該要有一個互相Check對方是否還”活者”的機制,例如利用Timer每隔一段時間向對方 送出alive的訊息,如果過了這一段時間仍然沒有任何訊息傳來,就應該判斷為連線已發生問題, 由我方主動決定是否要斷線或再繼續等待對方的回應,這才是比較適當的作法,網路的程式是 非常深奧的,請大家記住這一點

再重新說明一下網路對戰的大致流程
1.主方啟動遊戲,打開連線,等待客方加入
2.客方啟動連線,向主方送出連線訊息
3.主方接收到客方連線訊息,進行連線步驟,連線完成回送連線訊息給客方
4.主方進行遊戲設定向客方送出遊戲訊息
5.客方接收主方遊戲訊息,回覆主方
6.進行遊戲
7.遊戲結束或程式關閉,送出斷線訊息,另一方接收到斷線訊息亦進行斷線,結束遊戲
我們就先來看一下程式吧
請先看frmNetConnect的部份,這個表單用來設定你要連線的型態, 是要當主機,等待對方的連線,或者是連線到某一臺已開啟的主機。
確定了之後輸入資料按下確定,就開始連線的步驟

Private Sub cmdConnect_Click()
     '略

With frmGame

     程式連線的方法主要在這一段,在兩台電腦主機連線的時候,
     如果你是主機方(server)的話,必須用winsock物件的LocalPort方法指定一個Port
     來開放給對方使用,然後使用Listen方法來接收連線的通知,如果雙方指定的Port
     不一樣就無法順利連線了喔!但是要注意的是,在系統裡,有很多Port都已經是被
     指定用來做某些用途了,如果你指定這些Port來使用的話,可能會發生一些危險,
     一般來講,8000以後應該都是不會被使用的,那我怎麼用1001呢?
     嘿嘿...因為說明檔的範例是用這個Port,我就跟者用了,前面講的是後來別人
     告訴我的,大家還是小心點好
     如果是客方(client)的話,則必須要利用RemoteHost及RemotePort方法來指定
     要連線的主機及連線的Port,最後再送出Connent的訊息,接者就是等待連線了
     
     Select Case ConnectType
     Case CONNECT_SERVER
          .wskConnect.LocalPort = 1001
          .wskConnect.Listen
          .lblMyMessage.Caption = "等待連線中..."
     Case CONNECT_CLIENT
          .wskConnect.RemoteHost = txtIP
          .wskConnect.RemotePort = 1001
          .wskConnect.Connect
          .lblMyMessage.Caption = "向主機要求連線中..."
     End Select

     '略  一些控制項的控制
End With

Unload Me
End Sub


設定完成之後,我們就只有等待連線的訊息了,當連線訊息傳到主機方時
winsock會產生ConnectionRequest事件,我們就可以建立起與對方的連線了

Private Sub wskConnect_ConnectionRequest(ByVal requestID As Long)

當客方連線過來時,會傳遞一個requesID的值
我們只要使用Accept方法就可以建立起連線了
接者再使用SendMsgToUser傳送連線中的資料及
ResetConnectData重新設定一些遊戲相關資料就可以了
而客方則會產生Connect事件,我們也呼叫ResetConnectData就可以了
在這裡有一點要特別注意的是,我在一開始先偵測了winsock
的連線狀態,如果不是處於關閉狀態的話,就會先關閉,
這是一點預防連線忘記關閉的措施,但!這麼一來,如果有人正在
跟你對戰中的話.....
了解了吧?所以這裡最好詢問一下使用者比較好


If wskConnect.State <> sckClosed Then
   wskConnect.Close
   ConnectStatus = False
End If
'接受具有 requestID 參數的連線。
wskConnect.Accept requestID

Call SendMsgToUser(CONNECT_MSG & "連線中")
Call ResetConnectData
End Sub

這麼一來,第一~三步就完成了
1.主方啟動遊戲,打開連線,等待客方加入
2.客方啟動連線,向主方送出連線訊息
3.主方接收到客方連線訊息,進行連線步驟,連線完成回送連線訊息給客方


接者客方的控制功能都會被鎖住無法使用,必須等待主機的回應開始遊戲,
不過通話的部份則可以正常使用,接下來的步驟必須使用winsock的SendData及
DataArrival來接收對方傳來的資料,我們就依者步驟一節一節的來看

4.主方進行遊戲設定向客方送出遊戲訊息
Private Sub cmdGameStart_Click()

'略

當主方難度設定完成之後,按下開始遊戲,
我們便詢問玩家要讓對方猜的號碼,在檢查無誤之後
送出資料,等待對方的回應

SendMsgToUser我有制定好送出的格式,請自行觀看.bas的檔案,
裡面都有說明,基本上第一個送出Byte的是Message Type,如果是
遊戲相關的Game Message則加送一個Byte的Game Message,
最後再送出需要的相關Message
例如在這裡送出的就是指
SendMsgToUser GAME_MSG & MSG_GAME_START & Format$(GAME_NUMBER, "0") & UserNumber
GAME_MSG-遊戲訊息,MSG_GAME_START-遊戲開始,接者是要讓對方猜的難度和數字

If ConnectStatus = True Then
   SendMsgToUser GAME_MSG & MSG_GAME_START & Format$(GAME_NUMBER, "0") & UserNumber
   GameSetUP.Enabled = False
   cmdGameStart.Enabled = False
   Me.Caption = "請稍候...傳送資料中....正在等待對方回應....."
   Exit Sub
End If

'略
End Sub

接者是第五步
5.客方接收主方遊戲訊息,回覆主方
接收到資料時,會產生DataArrival,請看程式,

Private Sub wskConnect_DataArrival(ByVal bytesTotal As Long)
'接受對方資料的處理,判對第一個判別字元,再做對應的動作
'Normal - 對話資料, Connect - 連線成立,Game - 遊戲用資料,第二個字元再分類
'Close - 斷線

Dim GetMsg As String
Dim MsgType As String
wskConnect.GetData GetMsg, vbString
'ReDim dataArray(bytesTotal) As Byte            'ByteArray的接收法
'wskConnect.GetData dataArray(), vbBinaryCompare

我們使用GetData來接收資料,這裡必須指定接收的型態,我們以String的
方式來接受,除此之外,也可以使用Binary的方式來接收,自己依狀況來
決定接受的方式


MsgType = Left$(GetMsg, 1)
GetMsg = Right$(GetMsg, Len(GetMsg) - 1)
接者,分析接收到的是那種訊息,來決定下面的步驟

Select Case MsgType
Case NORMAL_MSG
     對話訊息,直接顯示在Label就可以了
     lblConnectUserMessage.Caption = GetMsg
Case CONNECT_MSG
     連線成立訊息
     lblMyMessage.Caption = GetMsg
Case GAME_MSG

     遊戲訊息,我們先看相關的部份就好

     Dim GameMsg As String
     GameMsg = Left$(GetMsg, 1)
     GetMsg = Right$(GetMsg, Len(GetMsg) - 1)
     Select Case GameMsg
     Case MSG_GAME_START
          詢問使用者是否願意接受對方提出的遊戲要求,願意的話,請使用者設定要讓
          對方猜的數字,檢查無誤的話,就送出 Client_Start的訊息,如果使用者拒絕
          的話,就送出Client_Chanel的訊息

          If MsgBox("主機端開啟了一場新遊戲,難度是" & Left$(GetMsg, 1) & "位數字,你要接受嗎?", vbYesNo Or vbQuestion, Me.Caption) = vbYes Then
             Dim x As String
             Call SetGAME_NUMBER(Left$(GetMsg, 1))
             UserNumber = ""
             Do Until UserNumber <> ""
                '略
             Loop
             '略
             SendMsgToUser GAME_MSG & MSG_CLIENT_START & UserNumber
          Else
             SendMsgToUser GAME_MSG & MSG_CLIENT_CANCEL_START
          End If

     Case ... '略
     End Select
Case CLOSE_MSG
     '略
End Select
Beep
End Sub

最後就是
6.進行遊戲(或對方不接受,中斷遊戲)
繼續看剛剛的程式接下來的部份

     Case MSG_CLIENT_START
          
          接受資料,以亂數決定先手,並將後手的input取消,
          一直到收到MSG_INPUT_OK或對方勝利

          If (GetRandomNo(9999) Mod 2) = 0 Then
             SendMsgToUser GAME_MSG & MSG_INPUT_OK
             DoEvents
             SendMsgToUser NORMAL_MSG & "你是先手"
          Else
             SendMsgToUser NORMAL_MSG & "我是先手"
             '略
          End If
     Case MSG_CLIENT_CANCEL_START
          '中斷,回到步驟四


接者就是wait,input,wait,input....一直循環 直到分出勝負,對方發出MSG_WIN為止
忘了一個Message的說明,當斷線的時候,我們要記得送出MSG_CLOSE, 告知對方斷線,這樣才不會一個人掛在那裡,什麼都不知道喔~~

結尾:
拖了這麼久,猜數字的教學文件終於完成了,還請多多見諒。 請期待作者的下一個作品吧~~Bye,Bye!!
回到VB教學教室