基于WPF實現(xiàn)經(jīng)典紙牌游戲
1 紙牌類
之所以產(chǎn)生這個無聊至極的念頭,是因為發(fā)現(xiàn)Unicode中竟然有這種字符。。。

這就意味著不用任何資源就可以實現(xiàn)一些紙牌游戲,效果如下圖所示

這就意味著不用任何資源就可以實現(xiàn)一些紙牌游戲,效果如下圖所示
#region 常量
private static readonly Dictionary<string, string[]> CardNames = new Dictionary<string, string[]>{
{"Spade", new string[13]{"??","??","??","??","??","??","??","??","??","??","??","??","??" } },
{"Heart", new string[13]{"??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??" } },
{"Diamond", new string[13]{"??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??" } },
{"Club" , new string[13]{"??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??", "??"}}
};
#endregion
#region 卡牌類型
private class Card
{
public Card(string name, int number, string type, bool red, int index)
{
Name =name;
Number = number;
Type = type;
Red = red;
Index = index;
}
public int Index;
public string Name;
public int Number;
public bool Red;
public string Type;
public int Region;
}
#endregion
在實現(xiàn)了紙牌類之后,將每個紙牌放到一個Button的Tag中,然后再 為Button添加各種事件,就能實現(xiàn)這個游戲了。
2 布局
由于是動態(tài)布局,所以建議使用Canvas,xaml界面十分簡潔,除了一個刷新按鈕,剩下的就只有畫布了。
<StackPanel>
<ToolBar DockPanel.Dock="Top" Margin="0 0 0 20">
<Button Content="??" Click="btnUpdate_Click"/>
</ToolBar>
<Canvas x:Name="cvMain" Height="400"/>
</StackPanel>
經(jīng)典紙牌游戲大致可以分為12個區(qū)域,如圖所示

這些個區(qū)域就可決定紙牌的位置,所以需要一個用來存放區(qū)間信息的變量
private List<int>[] cardIndex;
cardIndex是由12個List<int>組成的數(shù)組,然后每個Button的位置用下面的方式來設(shè)定
private void setBtnPosition(Button btn, int region)
{
Canvas.SetLeft(btn, region % 6 * dw);
Canvas.SetTop(btn, region / 6 * dh);
Canvas.SetZIndex(btn, cardIndex[region].Count);
}
其中,SetLeft即控件據(jù)Canvas左端的距離,可以理解為x坐標;dw,dh為全局變量,用來存放每個區(qū)間的尺寸。SetTop對應(yīng)的為y坐標。SetZIndex表示層級關(guān)系,值越大則越在上面。
3 初始化
初始化需要一個隨機數(shù)組,目的是將牌打散。這里用了一個非常Low的方案,即生成隨機數(shù),然后交換自然序列中兩個隨機數(shù)所在位置的值。
private int[] RandomArray(int length)
{
int[] arr = new int[length];
for (int i = 0; i < length; i++)
arr[i] = i;
int times = rand.Next(10, 100);
for (int i = 0; i < times; i++)
{
int a = rand.Next(0, length - 1);
int b = rand.Next(0, length - 1);
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
return arr;
}接下來就是初始化代碼,這里按照平時發(fā)牌的順序,先生成這個區(qū)域的紙牌

然后再生成牌堆。
public void InitCards()
{
cvMain.Children.Clear();
cards = new List<Card>();
cardIndex = new List<int>[12]; //所有的撲克被劃分為12個區(qū)域
for (int i = 0; i < 12; i++)
cardIndex[i] = new List<int>();
int index = 0;
foreach (var key in CardNames.Keys)
for (int i = 0; i < 13; i++)
cards.Add(new Card(CardNames[key][i], i, key,
key == "Heart" || key == "Diamond", index++));
var orders = RandomArray(52);
index = 0;
for (int i = 0; i < 6; i++)
for (int j = i; j < 6; j++)
{
var card = cards[orders[index++]];
cardIndex[6 + j].Add(cvMain.Children.Count);
var btn = setOneButton(card);
if (i == j)
{
coverCard(btn, false); //當i==j時翻面
SetOneColumn(i);
}
card.Region = 6 + j;
setBtnPosition(btn, card.Region);
}
while (index<52)
{
var card = cards[orders[index++]];
cardIndex[0].Add(cvMain.Children.Count);
var btn = setOneButton(card);
card.Region = 0;
setBtnPosition(btn, 0);
btn.Click += Card_Click;
}
}
其中,SetOneColumn用于下面牌的上下排序,定義為
private void SetOneColumn(int region)
{
var count = cardIndex[region].Count;
var left = (region - 6) * dw;
var top0 = dh;
int i = 0;
var ddh = (0.8 + 2.5 * count / 15) * dh / count;
foreach (var index in cardIndex[region])
{
var btn = cvMain.Children[index];
Canvas.SetLeft(btn, left);
Canvas.SetTop(btn, top0 + ddh * (i++));
Canvas.SetZIndex(btn, i);
}
}
4 事件
針對紙牌游戲來說,鼠標事件可分為兩類,一是點擊牌堆需要發(fā)牌;二是拖動其他位置的牌。
點擊牌堆
點擊牌堆需要注意,當牌堆中的牌沒有了之后,需要將1區(qū)的牌還給牌堆。
private void Card_Click(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
var card = btn.Tag as Card;
if (card.Region > 0)
return;
var count = cardIndex[0].Count;
var num = Math.Min(count, numCard);
for (int _ = 0; _ < num; _++)
{
var index = cardIndex[0][count - num]; //canvas中的順序
cardIndex[0].Remove(index);
cardIndex[1].Add(index);
btn = cvMain.Children[index] as Button;
btn.Click -= Card_Click;
btn.PreviewMouseLeftButtonDown += Card_PreviewLeftDown;
coverCard(btn, false);
setBtnPosition(btn, 1);
card = btn.Tag as Card;
card.Region = 1;
}
if (cardIndex[0].Count > 0 || cardIndex[1].Count <= numCard)
return;
foreach (var index in cardIndex[1])
{
cardIndex[0].Add(index);
btn = cvMain.Children[index] as Button;
btn.Click += Card_Click;
btn.PreviewMouseLeftButtonDown -= Card_PreviewLeftDown;
coverCard(btn, true);
setBtnPosition(btn, 0);
card = btn.Tag as Card;
card.Region = 0;
}
cardIndex[1] = new List<int>();
}
拖動
拖動主要包含三個動作,即鼠標按下、鼠標挪動、鼠標彈開,所以對應(yīng)三個函數(shù),且當鼠標按下之后,才掛載鼠標挪動的事件。而鼠標彈起之后,則判斷我們拖動的牌的最終位置。
private void Card_PreviewLeftDown(object sender, MouseButtonEventArgs e)
{
btnNow = sender as Button;
if (btnNow.Content.ToString() == bgCard)
return;
var card = btnNow.Tag as Card;
regionNow = cardIndex[card.Region];
indexNow = regionNow.IndexOf(
cvMain.Children.IndexOf(btnNow));
var count = regionNow.Count;
offsets = new List<Point>();
for (int i = indexNow; i < count; i++)
{
var btn = cvMain.Children[regionNow[i]] as Button;
offsets.Add(Mouse.GetPosition(btn));
Canvas.SetZIndex(btnNow, 100 + i);
}
btnNow.PreviewMouseLeftButtonUp += Card_PreviewLeftUp;
btnNow.PreviewMouseMove += Card_PreviewMouseMove;
}
private void Card_PreviewMouseMove(object sender, MouseEventArgs e)
{
var p = Mouse.GetPosition(cvMain);
for (int i = indexNow; i < regionNow.Count; i++)
{
var btn = cvMain.Children[regionNow[i]] as Button;
Canvas.SetLeft(btn, p.X - offsets[i - indexNow].X);
Canvas.SetTop(btn, p.Y - offsets[i - indexNow].Y);
}
}
private void Card_PreviewLeftUp(object sender, MouseButtonEventArgs e)
{
btnNow.PreviewMouseLeftButtonUp -= Card_PreviewLeftUp;
btnNow.PreviewMouseMove -= Card_PreviewMouseMove;
var p = Mouse.GetPosition(cvMain);
int region = (int)(p.X / dw) + (p.Y > dh ? 6 : 0);
var index = cardIndex[region].Count - 1; //目標區(qū)域最上面的牌的序號
var card = btnNow.Tag as Card;
int srcRegion = card.Region;
//牌在挪動之后有兩種可能,一種是成功了,另一種是失敗了
bool suc = region != srcRegion; //如果挪動的區(qū)域相同,則必失敗
bool subSuc;
suc &= region > 1; //如果向牌堆挪動,則必失敗。
if (index < 0)
{//A和K的情況滿足任何一種即可成功
subSuc = region > 1 && region < 6 && card.Number == 0;
subSuc |= region > 5 && card.Number == 12;
}
else
{
var tarBtn = cvMain.Children[cardIndex[region][index]] as Button;
var tarCard = tarBtn.Tag as Card;
var flag = tarCard.Type == card.Type;
var minus = card.Number - tarCard.Number;
subSuc = region > 1 && region < 6 && flag && (minus == 1);
subSuc |= region > 5 && (!flag) && (minus == -1);
}
reGroup(suc & subSuc, card, srcRegion, region);
}牌的去留
通過reGroup函數(shù)決定牌最終的狀態(tài)。
private void reGroup(bool suc, Card card, int srcRegion, int tarRegion)
{
if (suc)
{
if (tarRegion > 5)
setNewRegion(srcRegion, tarRegion);
else
setNewRegion(btnNow, card, srcRegion, tarRegion);
}
if (srcRegion < 6)
setBtnPosition(btnNow, srcRegion);
else
{
SetOneColumn(srcRegion);
var i = regionNow.Count;
if (i > 0)
{
var btn = cvMain.Children[regionNow[i - 1]] as Button;
coverCard(btn, false);
}
}
}
private void setNewRegion(Button btn, Card card, int src, int tar)
{
int i = cardIndex[src].Count - 1;
cardIndex[tar].Add(cardIndex[src][i]);
cardIndex[src].RemoveAt(i);
card.Region = tar;
setBtnPosition(btn, tar);
}
//src和tar均為大區(qū)
private void setNewRegion(int src, int tar)
{
var count = regionNow.Count;
for (int i = indexNow; i < count; i++)
{
var btn = cvMain.Children[regionNow[indexNow]] as Button;
cardIndex[tar].Add(regionNow[indexNow]);
regionNow.RemoveAt(indexNow);
var card = btn.Tag as Card;
card.Region = tar;
Canvas.SetZIndex(btn, cardIndex[tar].Count);
}
SetOneColumn(tar);
if (regionNow.Count > 0)
{
var btn = cvMain.Children[regionNow[indexNow - 1]] as Button;
coverCard(btn, false);
}
}
到此這篇關(guān)于基于WPF實現(xiàn)經(jīng)典紙牌游戲的文章就介紹到這了,更多相關(guān)WPF紙牌游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C# WPF如何反射加載Geometry幾何圖形數(shù)據(jù)圖標
這篇文章主要介紹了C# WPF如何反射加載Geometry幾何圖形數(shù)據(jù)圖標,幫助大家更好的理解和學(xué)習使用c#,感興趣的朋友可以了解下2021-03-03
C#調(diào)用百度API實現(xiàn)活體檢測的方法
這篇文章主要給大家介紹了關(guān)于C#調(diào)用百度API實現(xiàn)活體檢測的方法,文中通過示例代碼介紹的非常詳細,對大家學(xué)習或者使用C#具有一定的參考學(xué)習價值,需要的朋友們下面來一起學(xué)習學(xué)習吧2019-09-09
C# SendInput 模擬鼠標操作的實現(xiàn)方法
C# SendInput 模擬鼠標操作的實現(xiàn)方法,需要的朋友可以參考一下2013-04-04
C# OpenCvSharp實現(xiàn)去除字母后面的雜線
這篇文章主要為大家詳細介紹了C#如何使用OpenCvSharp實現(xiàn)去除字母后面的雜線效果,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習一下2023-11-11

