C#.NET中的21st Card Magic(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/st-card-magic-in-csharp-net-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 12 分钟阅读 - 5830 个词 阅读量 0C#.NET中的21st Card Magic(译文)
原文地址:https://www.codeproject.com/Articles/1155417/st-Card-Magic-in-Csharp-NET
原文作者:Mehedi Shams
译文由本站 robot-v1.0 翻译
前言
Coding a simple magic with cards!
用卡片编码一个简单的魔术!
介绍(Introduction)
21(21)圣(st)纸牌魔术是一种著名的纸牌戏法,使用21张纸牌进行.魔术师在3个牌组中对牌进行洗牌,并根据包含首选牌的牌组对牌进行重新洗牌.这是一个有趣的项目,描述了计算机程序中的技巧.(card magic is a famous card trick that is performed using 21 cards. The magician shuffles cards in 3 decks and re-shuffles the decks according to the deck that contains the preferred card. This is a fun project that depicts the trick in a computer program.)
背景-纸牌戏法(Background - The Card Trick)
我不会详细介绍这个技巧,因为网上有很多关于如何执行魔术的教程.总而言之,魔术师首先在3个套牌中对21张牌进行洗牌.卡牌保持上下颠倒,魔术师选择最后一张卡牌,然后将其放在1张(I shall not go into details of the trick as there are numerous tutorials on the net about how to perform the magic. To summarize, the magician first shuffles the 21 cards in 3 decks. Cards remain upside down, and the magician picks the last card, then places it on the 1)圣(st)牌组,然后选择下一张牌,放入2(deck, then picks the next card, puts in the 2)nd(nd)牌组,然后选择下一张卡片并放入3(deck, then picks the next card and puts in the 3)rd(rd)甲板.下一张卡将放置在1(deck. The next card will be placed on the 1)圣(st)甲板等等.(deck, and so on.) 改组后,魔术师一个接一个地挑选卡片组,并询问观察者所选卡片是否在卡片组中.无论哪一个卡座都有该卡,它都会进入其他两个卡座的中间.(After shuffling, the magician picks the decks one by one and asks the observer if the chosen card is in the deck. Whichever deck has the card, it goes in the middle of the other two decks.) 然后魔术师以相同的方式重新洗牌.(Then the magician re-shuffles the cards in the same way.) 经过3次随机播放后,魔术师开始从反面弹出卡,并且所选卡始终位于11张(After 3 shuffles, the magician starts popping the cards from the reverse side and the chosen card is always at the 11)日(th)位置.他/她将卡片显示给观察者,并使他/她感到困惑.(position. S/he shows the card to the observer and baffles him/her.) 关于卡如何始终将自己置于11位置有数学解释(There is a mathematical explanation of how the card always places itself at the 11)日(th)位置.快速仔细的观察是它是最终组合卡组的中间牌–前排10张,后排10张;所选的卡将自动置于中间.(position. A quick and careful observation is it is the middle card of the final combined deck – 10 cards at the front, 10 at the back; the chosen card is automatically put in the middle.)
本文中使用的关键字(Keywords Used in this Article)
- 甲板(Deck):一组卡(: A group of cards)
- 卡正面(Card-front):具有实际价值的卡面(: The side of a card that has the actual value)
- 返卡(Card-back):卡片背面没有任何显示;在所有卡片上都绘制类似的涂鸦,就像作为背景图像一样(: The backside of the card that doesn’t show anything; similar graffiti is painted across all the cards just as a background image)
- 魔术师(Magician):技巧的执行者(: The performer of the trick)
- 观察者(Observer):选择卡的人(: The person who chooses a card)
使用代码(Using the Code)
此项目使用了一些数据结构,即–(Some data structures are used for this project namely –) List
,(,) KeyValuePair
,(,) HashTable
和(and) Stack
.魔术产生以下步骤:(. The magic incurs the following steps:)
- 从资源文件加载卡.(Loading the cards from resource file.)
- 将卡片随机装入图片框中.(Load the cards in the pictureboxes randomly.)
- 允许用户选择卡;他/她通过单击包含卡片的按钮来确认这一点.(Allow the user to choose the card; s/he affirms this by clicking on the button that contains the card.)
- 用户选择该行后,将该行放置在其他两行的中间.(After the user chooses the row, place that row in the middle of the other two rows.)
- 允许用户选择3次. 3之后(Allow the user to choose 3 times. After the 3)rd(rd)改组,开始弹出卡并显示11(reshuffling, start popping the card and display the 11)日(th)从背面(或从正面开始-暂时没有关系,因为所选的卡片始终在中间).(card from reverse (or from the front – it doesn’t matter at this point as the chosen card is always in the middle).)
1)加载卡(1) Loading the Cards)
在设计时,使用图片框,并使用内置的IDE协助选择图像.(In design time, pictureboxes are used and images are selected using the in-built IDE assistance.)
这样,将图像加载到图片框中,IDE会自动将图像保留在资源文件中.(By doing this, images are loaded in the pictureboxes and the IDE automatically keeps the images in the resource file.)
有两个对象用于存储卡-一个是堆栈类型("(Two objects are used to store the cards – one is stack type (‘) ShuffledCards
‘), 另一个 ('(’), the other one (‘) Cards
’)是列表类型.两者都将卡存储为(’) is list type. Both stores cards as) KeyValuePair
对象.一种(objects. A) KeyValuePair
对象存储对象及其名称.例如.:(object stores an object along with its name. E.g.:) KeyValuePair
代表钻石王牌:{“钻石王牌”,钻石王牌图像}.(for Diamond Ace: {“Diamond Ace”, Image of diamond ace}.)
这个想法是在图片框中随机加载图像.首先,将所有资源加载到(The idea is to randomly load images in the pictureboxes. First, all the resources are loaded in a) ResourceSet
目的.然后将所有资源还原到"(object. Then all the resources are restored in the ‘) Cards
“列表.(’ list.)
ResourceSet resourceSet =
Properties.Resources.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
foreach (DictionaryEntry entry in resourceSet)
//Cards.Add(entry.Value);
Cards.Add(new KeyValuePair<string, object>(entry.Key.ToString(), entry.Value));
2)将卡片随机装入图片盒(2) Loading Cards in Picture Boxes Randomly)
加载图像资源后,我们选择将图像随机放置在图片框中.随机化器被初始化.生成从(包括1到21)的随机索引.请注意,如果您指定范围(1到22),它将生成一个介于1到21之间(含1和21)的随机数.实际上,我们需要一个介于(0到20)之间的索引,由于某种原因,从来没有生成0,所以我求助于这种生成介于(1到21之间)并减去1以获得实际索引的解决方案.(After loading the image resources, we opt for randomly placing the images in the pictureboxes. The randomizer is initialized. A random index from (1 to 21 inclusive) is generated. Please note that if you specify the range (1 to 22), it will generate a random number between 1 and 21 inclusive. Actually, we need an index between (0 to 20), for some reason 0 was never generated, so I resorted to this solution of generating between (1 and 21 inclusive) and then deduct 1 to obtain the actual index.)
图片框被命名为”(The pictureboxes are named as ‘) picturebox0
’,'(’, ‘) picturebox1
’,'(’, ‘) picturebox3
‘…..依此类推,直到’(’….. and so on up to ‘) picturebox20
’.(’.)
Random Rnd = new Random(DateTime.Now.Millisecond);
int Idx;
Hashtable HashTbl = new Hashtable();
...................
...................
for (int i = 0; i < 21; i++)
{
Idx = Rnd.Next(1, 22);
while (ThisImageWasAlreadyTaken(--Idx, HashTbl))
Idx = Rnd.Next(1, 22);
HashTbl.Add(Idx, Idx);
...................
...................
哈希表仅用于检查索引是否已被使用.方法(The hashtable is used just to check if the index was already taken. The method) ThisImageWasAlreadyTaken()
简单地返回(simply returns) true
/(/) false
根据哈希表中的索引可用性.(according to the index availability in the hash table.)
private bool ThisImageWasAlreadyTaken(int Idx, Hashtable HashTbl)
{
return HashTbl.ContainsKey(Idx);
}
图片框的放置方式如下图所示.现在是时候按图片框的名称查找它们并将随机图像放置在其中了.这在下面的代码中完成.请看(The pictureboxes are placed like below in the design form. Now it is time to find the pictureboxes by their name and place the random image there. This is done in the following code. Please see that) Controls.Find()
方法根据(method returns the control according to the) string
提供.第二个参数指示是否也应搜索子控件.设置为"(supplied. The second argument instructs whether it should search for child controls as well. This is set to ‘) false
“,因为我们知道没有子控件.(’ as we know that there are no child controls.)
Control[] PicBox;
PicBox = Controls.Find("PictureBox" + i, false);
(PicBox[0] as PictureBox).Image = (Image)Cards[Idx].Value;
ShuffledCards.Push(Cards[Idx]);
上面代码的最后一行是推入的堆栈操作.请注意,C#push-pop以标准方式运行,但是它存储值的方式需要一点注意.(The last line of the above code is a stack operation of pushing. Please note that C# push-pop operates in the standard way, however the way it stores the values requires a little attention.)
例如,对于如上图所示的第一次加载,(For example, for the first loading as shown in the above image, the) ShuffledCards
堆栈将包含:(stack will contain:)
[0]: {[Spade Jack, System.Drawing.Bitmap]}
[1]: {[Diamond 10, System.Drawing.Bitmap]}
[2]: {[Spade Ace, System.Drawing.Bitmap]}
[3]: {[Club Queen, System.Drawing.Bitmap]}
[4]: {[Heart 10, System.Drawing.Bitmap]}
[5]: {[Heart Queen, System.Drawing.Bitmap]}
[6]: {[Spade Queen, System.Drawing.Bitmap]}
[7]: {[Diamond Queen, System.Drawing.Bitmap]}
[8]: {[Club 10, System.Drawing.Bitmap]}
[9]: {[Diamond Jack, System.Drawing.Bitmap]}
[10]: {[Diamond Ace, System.Drawing.Bitmap]}
[11]: {[Spade King, System.Drawing.Bitmap]}
[12]: {[Heart Jack, System.Drawing.Bitmap]}
[13]: {[Heart King, System.Drawing.Bitmap]}
[14]: {[Diamond King, System.Drawing.Bitmap]}
[15]: {[Club Ace, System.Drawing.Bitmap]}
[16]: {[Club Jack, System.Drawing.Bitmap]}
[17]: {[Heart Ace, System.Drawing.Bitmap]}
[18]: {[Club King, System.Drawing.Bitmap]}
[19]: {[Spade 2, System.Drawing.Bitmap]}
[20]: {[Spade 10, System.Drawing.Bitmap]}
也就是说,它不断堆叠(如果我们考虑垂直堆叠).我们推的第一个是(That is, it keeps stacking over (if we think of a vertical stack). The first one we pushed was ‘) Spade 10
’,它已被推入第一个索引,但是随着我们不断推销其他内容,它最终将移至最后一个索引.无论如何,索引不是我们要处理的-堆栈中重要的是推/弹出.只是想重点介绍C#中堆栈的工作方式.堆栈上没有索引操作(尽管使用LINQ扩展方法(’, it was pushed in the first index, however as we keep pushing other stuff, it eventually moves to the last index. Anyway, index is not what we are dealing with – it is the push/pop that matters in stacks. Just wanted to focus some lights about how stacking works in C#. There is no index operations on stack (although a LINQ extension method) ElementAt()
可以使用,这不是堆栈的灵魂方法(can be used, which is not a soul method of stack)
3)用户互动(3) User Interaction)
观察者选择一张卡片并单击"行"按钮后,当前混洗的卡片会被复制到名为”(After the observer chooses a card and clicks on the row button, then the current shuffled cards are copied in a temporary list called ‘) OldShuffledCards
’.然后,如纸牌技巧所说,包含纸牌的行将进入其他两行的中间.这在下一行中完成(如果所选卡在第一行中).(’. Then, as the card-trick says, the row containing the card will go in the middle of the other two rows. This is done in the following line (if the chosen card is in the first row).)
现在,让我们看看这是如何完成的.如果我们看上述分布,假设观察者选择了" Heart Ace"(即2(Now let’s see how this is accomplished. If we look at the above distribution, let’s say the observer chooses ‘Heart Ace’ (which is the 2)nd(nd)从左上方的第一行开始),因此它位于第一行.因此,从技术上讲,该行中的所有卡将在其他两行之间移动.这是通过3个连续的循环完成的.(of the top row from left), thereby it is in the first row. So technically, all the cards in this row will move in between the other two rows. This is accomplished in 3 consecutive loops.)
第一个循环推入第二行的所有卡.如果我们仔细观察,在第一次迭代中,(The first loop pushes all the cards of the second row. If we look carefully, on the first iteration, the) OldShuffledCards[1]
被推,这是"(is pushed which is ‘) Diamond 10
’根据上述分布.同样,它将所有卡推入该行.(’ according to the above distribution. Similarly, it pushes all the cards in this row.)
下一个循环将推入第一行的所有卡.如果我们仔细观察,在第一次迭代中,(The next loop pushes all the cards of the first row. If we look carefully, on the first iteration, the) OldShuffledCards[2]
被推,这是"(is pushed which is ‘) Spade Ace
’根据上述分布.同样,它将所有卡推入该行.(’ according to the above distribution. Similarly, it pushes all the cards in this row.)
下一个循环将推送第三行的所有卡.如果我们仔细观察,在第一次迭代中,(The next loop pushes all the cards of the third row. If we look carefully, on the first iteration, the) OldShuffledCards[0]
被推,这是"(is pushed which is ‘) Spade Jack
根据上述分布.同样,它将所有卡推入该行.(’ according to the above distribution. Similarly, it pushes all the cards in this row.)
因此,所讨论的行位于其他两个之间.正如预期的那样,第一和第三循环可以互换-重要的是将有问题的行放在中间.(Thus the row in question is placed in between the other two. As expected, the first and third loops can be interchanged – what matters is keeping the row in question in the middle.)
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[1 + 3 * i]);
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[2 + 3 * i]);
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[3 * i]);
同样,如果所选卡位于2(In the same way, if the chosen card lies in the 2)nd(nd)行,则重新分配发生如下:(row, then the re-distribution occurs as below:)
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[3 * i]);
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[1 + 3 * i]);
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[2 + 3 * i]);
同样,如果选择的卡位于3(In the same way, if the chosen card lies in the 3)rd(rd)行,则重新分配发生如下:(row, then the re-distribution occurs as below:)
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[2 + 3 * i]);
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[3 * i]);
for (int i = 0; i < 7; i++)
ShuffledCards.Push(OldShuffledCards[1 + 3 * i]);
重新分发后,我们需要重新洗牌3个牌组上的卡.(After the re-distribution, we need to re-shuffle the cards on the 3 decks.)
为了重新分发,首先创建一个克隆堆栈,称为"(For re-distribution, a clone stack is first created called ‘) TempCards
’.此堆栈是我们全球的副本(’. This stack is a clone of our global) ShuffledCards
.之后,(. After that, the) ShuffledCards
被清除以容纳任何东西.然后,所有图片框(从0开始(is cleared out to hold nothing. Then, all pictureboxes (from 0)日(th)到20(to the 20)日(th))是通过从() are populated by popping the last card from the) TempCards
堆栈.同时,弹出卡被推入我们的全局(stack. At the same time, the popped card is pushed in our global) ShuffledCards
在进一步改组中将需要使用.这是通过以下代码完成的:(which will be required in further shuffles. This is done in the following code:)
Stack<KeyValuePair<string, object>> TempCards =
new Stack<KeyValuePair<string,
object>>(new Stack<KeyValuePair<string, object>>(ShuffledCards));
ShuffledCards.Clear();
Control[] PicBox;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 7; j++)
{
PicBox = Controls.Find("PictureBox" + (j + (i * 7)), false);
KeyValuePair<string, object> KVP = TempCards.Pop();
ShuffledCards.Push(KVP);
if (ShuffleCount < 2)
{
Thread.Sleep(20);
(PicBox[0] as PictureBox).Image = (Image)KVP.Value;
Application.DoEvents();
}
}
这是在前两个洗牌中完成的.故意提供一些延迟((This is done in the first two shuffles. A little delay is deliberately offered () Thread.Sleep(20);
),以便可以在显示屏上很好地观察卡片的重新排列.() so that the re-shuffling of cards can be well-observed on the display.)
最后一次改组怎么样?也可以执行此操作,但是要以狡猾的方式进行.如果我们省略此部分并删除(What about the last re-shuffling? This is also performed, but in a sly manner. If we omit this part and remove the) IF
在上述条件下,那么观察者将始终能够看到所选的卡始终将自己置于10(condition above, then the observer will always be able to see that the chosen card always places itself at the 10)日(th)任一方向的位置.因此,为了混淆他/她的理解,最后一次改组是在"(position from either direction. So to baffle his/her understanding, the last re-shuffle is done on the ‘) Cards
的堆栈,该堆栈最初一直将卡片保存在距资源文件较远的位置.这实际上是从我们的老朋友那里加载图片(’ stack which originally had been holding the cards so far from the resource file. This actually loads the images from our old pal) Cards
,而不是(, rather than the) ShuffledCards
**(该卡在11点保留所选的卡((which is holding the chosen card at the 11)日(th)现在的位置).请注意,上面的代码也完成了最后的改组;只是图像加载让我们有些狡猾.(position by now). Please note that the last re-shuffling was also done in the above code; it is just the image loading where we had been a little cunning.)
if (ShuffleCount == 2)
{
Hashtable HashTbl = new Hashtable();
int Idx;
Random Rnd = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 7; j++)
{
PicBox = Controls.Find("PictureBox" + (j + (i * 7)), false);
Idx = Rnd.Next(1, 22);
while (ThisImageWasAlreadyTaken(--Idx, HashTbl))
Idx = Rnd.Next(1, 22);
HashTbl.Add(Idx, Idx);
(PicBox[0] as PictureBox).Image = (Image)Cards[Idx].Value;
Thread.Sleep(20);
Application.DoEvents();
}
}
最后,如果随机播放计数达到3,则11(Finally, if the shuffle count reaches 3, then the 11)日(th)卡刚刚显示.(card is just displayed.)
private void DisplayCard()
{
Control[] PicBox = Controls.Find("ChosenCardpictureBox", false);
for (int i = 0; i < 10; i++)
ShuffledCards.Pop();
KeyValuePair<string, object> KVP = ShuffledCards.Pop();
(PicBox[0] as PictureBox).Image = (Image)KVP.Value;
PlayAgainButton.Visible = true;
StatusLabel.Text = "Look I found your card!...... Magic!!";
Row1Button.Enabled = Row2Button.Enabled = Row3Button.Enabled = false;
}
环境(Environment)
该项目是在带有目标.NET Framework 4.5的Visual Studio 2015 IDE中完成的.(The project was accomplished in Visual Studio 2015 IDE, with target .NET Framework 4.5.)
未来作品(Future Works)
动画效果可能会应用在卡从其位置移动到组合卡座,然后再次分布在单独卡座中的情况.可以将自己房屋中的牌组随机分配,以使观察者感到更加困惑(即,在每行中,可能会随机放置7张牌,但是原始磁道必须保持在单独的阵列中).(Animation effects might be applied where a card moves from its position to the combined deck, then again distributed in separate decks. The decks in their own premises can be randomised to expose a more baffling effect to the observer (i.e., in each row, the 7 cards might be randomly placed, however the original track must be maintained in a separate array).)
替代数据结构(Alternative Data Structures)
键值对不是必须的组成部分,可以使用简单的列表,因为未使用卡名.这用来更有效地演示该项目.所使用的图像可以放在列表中并加以利用.(The Key-value pair is not a must component, a simple list can be used because the card-names were not used. This was used to demonstrate the project more effectively. It is the images that are used which can be put in a list and utilized thereby.)
代替(Instead of) Stack
,可以使用一个简单的列表.(, a simple list might be used.)
参考文献(References)
- 堆栈克隆(Stack Cloning)
- 从资源中阅读(Reading from Resources)
- 在堆栈中使用索引(Use of Index in stack))
- Winforms的响应式设计技术(A responsive design technique for Winforms)
历史(History)
- 11(11)日(th)2016年11月:首次发布(November, 2016: First release)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# .NET4.5 .NET VS2013 Marketing Dev windows-forms 新闻 翻译