目录
- 1 纸牌类
- 2 布局
- 3 初始化
- 4 事件
- 点击牌堆
- 拖动
- 牌的去留
1 纸牌类
之所以产生这个无聊至极的念头,是因为发现Unicode中竟然有这种字符。。。
这就意味着不用任何资源就可以实现一些纸牌游戏,效果如下图所示
这就意味着不用任何资源就可以实现一些纸牌游戏,效果如下图所示
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]{"🃑", "🃒", "🃓", "🃔", "🃕", "🃖", "🃗", "🃘", "🃙", "🃚", "🃛", "🃝", "🃞"}} | |
}; | |
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; | |
} | |
在实现了纸牌类之后,将每个纸牌放到一个Button的Tag中,然后再 为Button添加各种事件,就能实现这个游戏了。
2 布局
由于是动态布局,所以建议使用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> |
经典纸牌游戏大致可以分为12个区域,如图所示
这些个区域就可决定纸牌的位置,所以需要一个用来存放区间信息的变量
private List<int>[] cardIndex;
cardIndex是由12个List<int>组成的数组,然后每个Button的位置用下面的方式来设定
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即控件据Canvas左端的距离,可以理解为x坐标;dw,dh为全局变量,用来存放每个区间的尺寸。SetTop对应的为y坐标。SetZIndex表示层级关系,值越大则越在上面。
3 初始化
初始化需要一个随机数组,目的是将牌打散。这里用了一个非常Low的方案,即生成随机数,然后交换自然序列中两个随机数所在位置的值。
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; | |
} |
接下来就是初始化代码,这里按照平时发牌的顺序,先生成这个区域的纸牌
然后再生成牌堆。
public void InitCards() | |
{ | |
cvMain.Children.Clear(); | |
cards = new List<Card>(); | |
cardIndex = new List<int>[12]; //所有的扑克被划分为12个区域 | |
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 事件
针对纸牌游戏来说,鼠标事件可分为两类,一是点击牌堆需要发牌;二是拖动其他位置的牌。
点击牌堆
点击牌堆需要注意,当牌堆中的牌没有了之后,需要将1区的牌还给牌堆。
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>(); | |
} |
拖动
拖动主要包含三个动作,即鼠标按下、鼠标挪动、鼠标弹开,所以对应三个函数,且当鼠标按下之后,才挂载鼠标挪动的事件。而鼠标弹起之后,则判断我们拖动的牌的最终位置。
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; //目标区域最上面的牌的序号 | |
var card = btnNow.Tag as Card; | |
int srcRegion = card.Region; | |
//牌在挪动之后有两种可能,一种是成功了,另一种是失败了 | |
bool suc = region != srcRegion; //如果挪动的区域相同,则必失败 | |
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
函数决定牌最终的状态。
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均为大区 | |
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); | |
} | |
} |