本文是對C#網絡版斗地主的開發總結。
出牌順序如上圖所示。
出牌權限可以用一個bool值表示
在Player類中,有一個屬性:haveOrder表示玩家是否有權限出牌。
還需要考慮到一點,當一個玩家出牌後,其他玩家都要不起(pass),該玩家不能自己“要不起”自己,所以還需要一個bool類型的屬性“IsBiggest”。
該屬性表示自己出的牌最大。怎樣保證該值的有效性呢?換句話說,最大的只能有一個。所以,每當自己出牌的時候,只要自己的牌比別人的牌大,就把該值設置為true。當該值為true時,向其他玩家發送信息表明自己的牌比你們的大,其他玩家收到信息後,把自己的IsBiggest屬性設置為false;如此一來,就實現了出牌權限的正確傳遞。
具體實現方法如下:
本文需要Player類中的一些代碼:
private bool _haveOrder;
public bool haveOrder
{
get
{
return _haveOrder;
}
set
{
_haveOrder = value;
}
}
private bool _isBiggest;
public bool isBiggest //
{
get
{
return _isBiggest;
}
set
{
if (value)//給IsBiggest賦值true時
{
if (DConsole.client != null) //當玩家為客戶端時
{
DConsole.client.SendDataForServer("IamIsBiggest");//服務器這邊是怎麼處理的呢?請看下文.
_isBiggest = value;
}
if (DConsole.server != null)//如果玩家為服務器
{
DConsole.server.SendDataForClient("NoBiggest", 1);
DConsole.server.SendDataForClient("NoBiggest", 2);//告訴客戶端,你不是Biggest,客戶端如何處理?請看下文
_isBiggest = value;
}
}
else
{
_isBiggest = value;
}
}
}
public bool lead()//出牌方法
{
DConsole.leadPokers = leadPokers;
this.leadPokers.Clear();
foreach (int selectPoker in this.selectPokers) //迭代循環把已選中的牌添加到leadPokers
{
this.leadPokers.Add(this.pokers[selectPoker]);
}//以上代碼是將選中的牌構造成一個PokerGroup對象
if (DConsole.IsRules(this.leadPokers))//驗證選中的牌是否符合游戲規則,關於規則在以後的文章中詳細講解
{
if (DConsole.player1.isBiggest || DConsole.leadPokers > DConsole.leadedPokerGroups[DConsole.leadedPokerGroups.Count-1]) //玩家能出牌的兩種情況是1、自己上輪出的牌是最大的,別人都要不起。2、自己本輪出的牌比別人的大。
{
if (DConsole.leadPokers.type == PokerGroupType.炸彈)//當牌組的類型為炸彈時,翻倍
{
DConsole.multiple *= 2;
}
if (DConsole.leadPokers.type == PokerGroupType.雙王) //當牌組的類型為雙王時,翻三倍
{
DConsole.multiple *= 3;
}
DConsole.player1.isBiggest = true;//只要自己的牌出出去了,就假設自己是最大的,一旦自己的牌被別人管上,就會被設置為false,具體請看下文
this.BakPoker(); //備份現有pokers,下次出牌時需要用到
foreach (int selectPoker in this.selectPokers) //在pokers裡移除已經出過的牌
{
this.pokers.Remove(this.bakPokers[selectPoker]);
}
this.selectPokers.Clear(); //清空已選牌
return true;
}
else
{
this.leadPokers.Clear();
return false; //牌組比別人小就返回false
}
}
else
{
this.leadPokers.Clear();
return false; //不符合規則就返回false
}
}
haveOrder(出牌權限)傳遞的具體實現:
在服務器選擇地主時,把出牌權限一並傳給地主,所以出牌是由地主開始的。這裡我先假設地主為服務器。
服務器在叫地主後,出了本局第一組牌,下面看一下出牌按鈕的處理程序:
private void btnLead_Click(object sender, EventArgs e) //出牌按鈕的事件處理程序
{
if (player1.lead())//嘗試出牌,該方法的代碼見上文,如果出牌成功,執行以下代碼
{
this.btnLead.Visible = false;
this.btnPass.Visible = false;//隱藏出牌和不要按鈕
if (this.server != null) //當玩家為服務器時
{
server.SendDataForClient("SPokerCount" + Convert.ToString(this.player1.pokers.Count), 1); //發送自己牌的剩余張數給client1
Thread.Sleep(100);//延遲100毫秒
server.SendDataForClient("SPokerCount" + Convert.ToString(this.player1.pokers.Count), 2);發送自己牌的剩余張數給client2
Thread.Sleep(100);
server.SendDataForClient("server", DConsole.leadPokers, 1);//發送自己出的牌組給client1
Thread.Sleep(100);
server.SendDataForClient("server", DConsole.leadPokers, 2);//發送自己出的牌組給client2
Thread.Sleep(100);
if (this.player1.pokers.Count == 0 && DConsole.IsStart) //當server端牌的張數為0並且游戲已經開始時
{
DConsole.Winer = 1; //勝利者為1,即server,這裡是計分系統,很簡單,不贅述
DConsole.Restart();//重新開始游戲
}
else
{
server.SendDataForClient("Order", 2); //這裡就是傳遞權限的關鍵代碼,當server端出牌並且牌沒有出完時,傳遞出牌權限給server端的下家,根據權限傳遞順序,server-client2-clinet1,所以這裡把出牌權限傳遞給client2
}
DConsole.player1.haveOrder = false; //自己的出牌權限已經消失
}
if (this.client != null) //當玩家為客戶端時
{
client.SendDataForServer("PokerCount" + Convert.ToString(this.player1.pokers.Count)); //發送客戶端牌的剩余張數給服務器,服務器會處理並轉發給另一個客戶端
Thread.Sleep(500);
client.SendDataForServer("client", DConsole.leadPokers);//發送客戶端出牌牌組給服務器,服務接收到該消息後,就知道客戶端1或者客戶端2已出牌,如果該消息是客戶端1發出的,根據權限傳遞順序client2-clinet1-server,服務器會獲得出牌權限,如果該消息是客戶端2發出的,服務器會發送消息給客戶端1使客戶端1擁有出牌權限
Thread.Sleep(100);
this.player1.haveOrder = false; //自己的出牌權限消失
}
player1.g.Clear(this.BackColor);
player1.Paint();
DConsole.PaintPlayer1LeadPoker();
}
else
{
DConsole.Write("[系統消息]:您出的牌不符合規則!");
}
}
來看看server類中有關出牌權限傳遞的相關處理程序:
/// <summary>
/// 循環接收客戶端1的請求數據
/// </summary>
public void AccpetClient1Data()
{
NetworkStream Ns1 = client1.GetStream();
string str1 = "";
while (true)
{
PokerGroup pg = new PokerGroup();
byte[] bytes1 = new byte[108];
Ns1.Read(bytes1, 0, 108);
str1 = Encoding.Default.GetString(bytes1);
//(省略N行)
if (str1.StartsWith("client")) //收到客戶端1的client消息,該消息表示客戶端1出的牌
{
SendDataForClient(str1, 2);
Thread.Sleep(sleep);
str1 = str1.Replace("client", "");
pg.GetPokerGroup(Encoding.Default.GetBytes(str1));
DConsole.leadedPokerGroups.Add(pg); //這裡在add之前會對該牌組進行驗證和排序
DConsole.PaintPlayer2LeadPoker(pg);
DConsole.WriteLeadedPokers();
if (!DConsole.IsRestart)
{
DConsole.player1.haveOrder = true; //client1出牌後歸server出牌,前提是沒有Restart,Restart後出牌權限消失,根據權限傳遞順序client2-clinet1-server,這裡接收到的是client1的出牌,說明輪到server出牌了.
}
else
{
DConsole.IsRestart = false;//當檢測到已經Restart時,復位Restart使它還原為false
}
continue;
}
//Client放棄出牌,權限交給服務器
if (str1.StartsWith("Pass"))
{
DConsole.gPlayer2LeadPoker.Clear(DConsole.backColor);
DConsole.gPlayer2LeadPoker.DrawString("不要", new System.Drawing.Font("宋體", 20), System.Drawing.Brushes.Red,5,5);
DConsole.player1.haveOrder = true;
SendDataForClient("ClientPass", 2); //告訴客戶端2,客戶端1pass了
continue;
}
if (str1.StartsWith("IamIsBiggest"))//客戶端1說他的牌是最大的,所以要把自己的IsBiggest設置為false,因為最大的牌只能有一個
{
DConsole.player1.isBiggest = false;
this.SendDataForClient("NoBiggest", 2); //同時要轉發給client2,讓client2把自己的IsBiggest屬性設置為false
continue;
}
//(省略N行)
}
}
/// <summary>
/// 循環接收客戶端2的請求數據
/// </summary>
public void AccpetClient2Data()
{
NetworkStream Ns2 = client2.GetStream();
string str1 = "";
while (true)
{
PokerGroup pg = new PokerGroup();
byte[] bytes2 = new byte[108];
Ns2.Read(bytes2, 0, 108);
str1 = Encoding.Default.GetString(bytes2);
(省略N行)
if (str1.StartsWith("client"))//收到客戶端2的client消息,該消息表示客戶端2出的牌
{
SendDataForClient(str1, 1);
Thread.Sleep(sleep);
str1 = str1.Replace("client", "");
pg.GetPokerGroup(Encoding.Default.GetBytes(str1));
DConsole.leadedPokerGroups.Add(pg);//這裡在add之前會對該牌組進行驗證和排序
DConsole.PaintPlayer3LeadPoker(pg);
DConsole.WriteLeadedPokers();
if (!DConsole.IsRestart)
{
SendDataForClient("Order", 1); //client2出牌後歸client1出牌,前提是沒有Restart,Restart後出牌權限消失,根據權限傳遞順序server-client2-clinet1,這裡接收到的是client2的出牌,說明輪到clinet1出牌了.
}
else
{
DConsole.IsRestart = false; //當檢測到已經Restart時,復位Restart使它還原為false供下次使用
}
System.Threading.Thread.Sleep(sleep);
continue;
}
//Client2放棄出牌,權限交給Client1
if (str1.StartsWith("Pass"))
{
DConsole.gPlayer3LeadPoker.Clear(DConsole.backColor);
DConsole.gPlayer3LeadPoker.DrawString("不要", new System.Drawing.Font("宋體", 20), System.Drawing.Brushes.Red, 5, 5);
SendDataForClient("ClientPass", 1);//告訴客戶端1,客戶端2pass了
Thread.Sleep(500);
SendDataForClient("Order", 1);
continue;
}
if (str1.StartsWith("IamIsBiggest"))//客戶端2說他的牌是最大的,所以要把自己的IsBiggest設置為false,因為最大的牌只能有一個
{
DConsole.player1.isBiggest = false;
this.SendDataForClient("NoBiggest", 1);//同時要轉發給client21,讓client1把自己的IsBiggest屬性設置為false
continue;
}
(省略N行)
}
}
Client類中有關出牌權限傳遞的代碼:
public void AcceptServerData()
{
NetworkStream Ns = client.GetStream();
string str = "";
while (true)
{
byte[] bytes = new byte[108];
Ns.Read(bytes, 0, 108);
str = Encoding.Default.GetString(bytes);
if (str.StartsWith("Order")) //收到這條消息即表示自己有出牌權限了
{
DConsole.player1.haveOrder = true;
continue;
}
if (str.StartsWith("ClientPass")) //另一個客戶端pass後,在窗體中表示出來
{
DConsole.gPlayer3LeadPoker.Clear(DConsole.backColor);
DConsole.gPlayer3LeadPoker.DrawString("不要", new System.Drawing.Font("宋體", 20), System.Drawing.Brushes.Red, 5, 5);
continue;
}
if (str.StartsWith("ServerPass"))//服務器pass後,在窗體中表示出來
{
DConsole.gPlayer2LeadPoker.Clear(DConsole.backColor);
DConsole.gPlayer2LeadPoker.DrawString("不要", new System.Drawing.Font("宋體", 20), System.Drawing.Brushes.Red, 5, 5);
continue;
}
if (str.StartsWith("NoBiggest")) //當自己的牌被別人打了後,別人會發送該消息給自己,這時設置自己的Isbiggest為false
{
DConsole.player1.isBiggest = false;
continue;
}
}
以上就是出牌權限傳遞的具體實現,這些代碼之間是相互關聯的,形成一個回路。除非有人的牌出完了,否則就會一直傳遞下去。代碼比較多,如果哪裡有錯誤的話歡迎留言反饋,謝謝!