杂物盘点库(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/cribbage-hand-counting-library-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 15 分钟阅读 - 7065 个词 阅读量 0杂物盘点库(译文)
原文地址:https://www.codeproject.com/Articles/15468/Cribbage-Hand-Counting-Library
原文作者:Sean Michael Murphy
译文由本站 robot-v1.0 翻译
前言
A library to score cribbage hands
一个为记分牌评分的图书馆
背景(Background)
由于MSN游戏从其在线游戏列表中删除了多人游戏,因此我一直对编写自己的游戏感兴趣.与该目标相关的问题很多,但是核心要求之一就是得分方法.在使用Google搜索现有评分算法并没有找到任何算法之后,我着手编写自己的算法.我将向您展示如何编写库来记下纸牌,并在此过程中分享一些挑战.(Since MSN games removed multi-player cribbage from their list of online games, I’ve been interested in writing my own. There are many interesting problems associated with that goal, but one of the core requirements would be a way to score a hand. After Googling for existing scoring algorithms and not finding any, I set about writing my own. I’ll show you how to write a library to score a cribbage hand, and share a couple of the challenges along the way.)
游戏(The Game)
可以找到游戏的绝佳概述(An excellent overview of the game can be found) 这里(here) ,因此我不会赘述过多.(, so I won’t go too far into the details.) 简而言之,这是一个在两个或三个人之间玩的纸牌游戏(为简单起见,这里我只考虑两人游戏).每位玩家被分配6张牌,并且必须将两张牌交到一个秘密牌局(有时称为"小猫"或"小床"),随后由发牌者得分.一旦每个人都向卡牌中添加了卡,便从卡组上切下一张社区卡,以供所有玩家稍后得分时使用.然后,玩家在得分的第一阶段打出自己的纸牌,以获取"挂钩点".一旦所有卡牌都已打完,非经销商玩家就对他们的牌进行得分并将其得分加到他们的游戏总数中.发牌者先得分,然后再得分.第一位获得121胜的球员.(Very briefly, it is a card game played between two or three people (for simplicity, I’ll only consider two-player games here). Each of the players is dealt 6 cards, and must contribute two cards to a secret hand (sometimes called the “kitty” or the “crib”), later scored by the dealer. Once everyone has added cards to the kitty, a community card is cut from the deck for later use by all the players in scoring their hands. The players then play their cards in the first phase of scoring for “pegging points”. Once all the cards have been played, the non-dealer player scores their hand and adds their points to their game total. The dealer scores his hand, then scores his kitty. The first player to get 121 wins.) 这是我们感兴趣的一轮结束时的手牌得分.(It is the scoring of the hand at the end of the round that interests us here.)
计分(Scoring)
“手"是玩家所持的四张牌,再加上"割"牌.对于大多数计分事件,手牌和切牌之间的区别是无关紧要的,因此可以将手牌主要分为五张牌的一个单位.我会在下面指出情况的不同之处.如果这只手是小猫,则得分上也有细微的差别,我也将在短期内强调该差别.(A “hand” is the four cards the player is holding, plus the “cut” card. For most scoring events, the distinction between the cards of the hand and the cut card is irrelevant, so the hand can be scored mainly as a single unit of five cards. I’ll point out the situation when it makes a difference below. There is also a slight difference in scoring if the hand is a kitty, and I’ll highlight that difference shortly too.) 有五种方法可以赢得积分.(There are five ways to win points scoring a cribbage hand:)
对(Pair) | 玩家每手对得两分.(For every pair in the hand, the player scores two points.) |
---|---|
15 | 对于每张增加15张的纸牌组合,玩家得分为2分.(For every combination of cards that adds to 15, the player scores two points.) |
Runs | 对于3张,4张或5张纸牌中的每张纸牌,玩家得分为1分.(For each card in a run of 3, 4, or 5 cards, the player scores one point.) |
Flush | 如果一手的四张牌是相同的牌,则玩家得分为4分.如果他们也与切牌相符,则玩家得分为5.如果玩家得分,则所有五张牌必须匹配并得分五分.(If the four cards of a hand are the same suit, the player scores four points. If they also match the cut card, the player scores five. If a player is scoring their kitty though, all five cards must match and score five points.) |
Nobs | 当我被教怎么玩时,这总是被称为"正确的杰克”,但是如果您手中有四张纸牌,并且与裁切纸牌相同,那么您得一分.(This was always called “the right Jack” when I was taught how to play, but if you have a Jack in your hand of four cards, and it is the same suit as the cut card, you score one point.) |
我的卡类(My Card Class)
为了(For the) 格栅(grillionth) 在计算机历史上,我们需要一个(time in the history of computers, we need a) Card
类.这已经做过很多次了,我很惊讶没有标准(class. This has been done so many times, I’m faintly surprised there isn’t a standard) Card
.NET Framework基类中的class.反正我的(class in the base classes of the .NET Framework. Anyway, my) Card
类具有几个构造函数:(class has a couple of constructors:)
public Card(Int32 index)
public Card(char name, char suit)
public Card(string card) // card[0] = name, card[1] = suit
建成后,(Once constructed, the) Card
展示有助于得分的几个属性.(exposes a couple of properties that aid in scoring.)
名称(Name) | 类型(Type) | 描述(Description) |
---|---|---|
DeckIndex |
Int32 |
卡在卡座中的索引.从0到51.Ace-> King,Spades,Hearts,Clubs,Diamonds.(The index of the card in the deck. From 0 to 51. Ace -> King, Spades, Hearts, Clubs, Diamonds.) |
Name |
char |
{A,2,3,4,5,6,7,8,9,T,J,Q,K}中的一个.(One of {A, 2, 3, 4, 5, 6, 7, 8, 9, T, J, Q, K}.) |
Suit |
char |
{S,H,C,D}之一.(One of {S, H, C, D}.) |
Ordinal |
Int32 |
代表西装中卡片的索引(即0-12).(Represents the index of the card within the suit (i.e., 0 - 12).) |
Value |
Int32 |
返回一张卡的计数值(Ace =1,面卡=10).(Returns the counting value of a card (Ace = 1, face cards = 10).) |
类实现(The class implements) System.IComparable
,因此您可以轻松地对它们的数组进行排序.(, so you can sort arrays of them easily.)
评分算法(Scoring Algorithms)
得分的基本要求包括将4张和5张卡分成几组进行分析.例如,要计算一手牌中的对数,您需要一次将每张卡与另一张卡进行比较.您需要从五个(手)的集合中详尽地列出长度为2的子集.(The basic requirement to score the hand involves breaking the groups of 4 and 5 cards down into smaller sets for analysis. To count the number of pairs in a hand, for instance, you need to compare each card to each other card in the hand, once. You want an exhaustive list of the subsets of length two from your set of five (the hand).)
几个月前,当我开始考虑这个问题时,我开始在.NET库中寻找功能来破解(When I started thinking about this problem a couple of months ago, I began looking for functions in the .NET libraries to break) Array
s或(s or) List<>
分成较小的子集.一无所获,我开始用谷歌搜索通用算法来做到这一点.(into smaller subsets. Not finding any, I started Googling generic algorithms for doing this.)
然后,我意识到5个一组中2个子集的数量实际上是一个有限的,而且确实很小.由于在5个一组中只有2个的10个子集,所以我创建了这些子集的数组.我并没有试图给任意大的纸牌得分(尽管这也是一个有趣的问题),而是给一只总是大小等于5的牌得分.不是在运行时计算子集,而是将它们硬编码为数组.(Then, I realized that the number of sub-sets of 2 in a group of 5 is actually a finite, and indeed small, number. Since there are only 10 subsets of 2 in a group of 5, I created an array of those subsets. I’m not trying to score an arbitrarily large cribbage hand (although that would be an interesting problem too), I’m scoring a hand where size always equals 5. Rather than calculating the subsets at run-time, I hard coded them into an array.)
图像1.一组5手中的2个.(Image 1. Sets of 2 in a hand of 5.)所以如果我有五个数组(So if I have an array of five) Card
对象,"(objects, the “) Sets of 2
“数组包含要比较的索引对的列表.得分对的伪代码如下所示:(” array contains a list of pairs of indices to compare. The pseudo-code for scoring pairs would look like this:)
for (Int32 i = 0; i < setsOf2.Length; i++)
if (card[setsOf2[i][0]].Name == card[setsOf2[i][1]].Name)
AddPairToScore(card[setsOf2[i][0]], card[setsOf2[i][1]]);
如上文所述,"(As I explained above, the “) Name
卡中的”(” of a card is one of) {A, 2, 3, 4, 5, 6, 7, 8, 9, T, J, Q, K}
.(.) setsOf2[i][0]
指向i中的第一个数组索引(points to the first array index in the i)日(th)的元素(element of the) setsOf2
数组.(array.) setsOf2[i][1]
指向第二个.如果(points to the second. If the) Card
这两个索引所指的手中的对象是相同的"类型"(即它们都是A或King),那么您找到了一对.当…的时候(objects in the hand pointed to by those two indices is the same “type” (i.e., they’re both Aces or Kings), then you’ve found a pair. When the) i
循环完成后,您已经将所有卡对彼此进行了检查,并且找到了所有可能的对.(loop is finished, you’ve checked all of the cards against each other, and all of the possible pairs have been found.)
配对非常容易得分,因为它们只能(按定义)涉及2组.对15s进行评分的算法稍微复杂一些,因为15s可以由2\3\4或全部5张牌组成.我添加了名为(Pairs are pretty easy to score, since they can only (by definition) involve sets of 2. The algorithm to score 15s is slightly more complicated, since 15s can be made from 2, 3, 4, or all 5 cards. I added arrays called) setsOf3
和(and) setsOf4
包含子集各自长度的索引.(that encompass the indices of subsets for their respective lengths.)
图像2.套在3手5中.(Image 2. Sets of 3 in a hand of 5.)
图像3.一组5手中的4.(Image 3. Sets of 4 in a hand of 5.)因此寻找15s的算法如下所示:(So the algorithm to look for 15s looks like this:)
// Look for 15s made up of 2 cards
for (Int32 i = 0; i < setsOf2.Length; i++)
if (card[setsOf2[i][0]].Value + card[setsOf2[i][1]].Value == 15)
Add15ToScore(card[setsOf2[i][0]], card[setsOf2[i][1]]);
// Look for 15s made up of 3 cards
for (Int32 i = 0; i < setsOf3.Length; i++)
if (card[setsOf3[i][0]].Value + card[setsOf3[i][1]].Value +
card[setsOf3[i][2]].Value == 15)
Add15ToScore(card[setsOf3[i][0]], card[setsOf3[i][1]], card[setsOf3[i][2]]);
// Look for 15s made up of 4 cards
for (Int32 i = 0; i < setsOf4.Length; i++)
if (card[setsOf4[i][0]].Value + card[setsOf4[i][1]].Value +
card[setsOf4[i][2]].Value + card[setsOf4[i][3]].Value == 15)
Add15ToScore(card[setsOf4[i][0]], card[setsOf4[i][1]],
card[setsOf4[i][2]], card[setsOf4[i][3]]);
// Look for 15s made up of 5 cards. Trivial case.
for (Int32 i = 0; i < setsOf5.Length; i++) // Length == 1
if (card[setsOf5[i][0]].Value + card[setsOf5[i][1]].Value +
card[setsOf5[i][2]].Value + card[setsOf5[i][3]].Value +
card[setsOf5[i][4]].Value == 15)
Add15ToScore(card[setsOf5[i][0]], card[setsOf5[i][1]],
card[setsOf5[i][2]], card[setsOf5[i][3]], card[setsOf5[i][2]]);
一旦我编码了这种残酷性,我意识到应该将单独的阵列折叠成一个阵列,并且超级阵列的第一个索引应该是要比较的卡数.一旦这样做,可以将15个计数的伪代码简化为:(Once I had coded that atrocity, I realized that the separate arrays should be collapsed into a single array, and the first index of the super-array should be the number of cards being compared. Once I did that, the 15-counting pseudo-code could be shortened to this:)
for (Int32 i = 2; i <= 5; i++) {
for (Int32 j = 0; j < sets[i].Length; j++) {
for (Int32 k = 0; k < sets[i][j].Length; k++)
sum += card[sets[i][j][k]].Value
if (sum == 15)
Add15ToScore(cardsPointedToByIndicies sets[i][j]);
}
}
冲洗是微不足道的.遍历一手牌,并且当一副牌的花色不等于手中第一张牌的花色时,退出并给玩家打分0.如果您通过循环结束,则表示所有的衣服都是一样的,并且玩家有权获得同花的数目.请记住,所有五张牌都必须匹配才能在小猫中得分,但只有四张手牌需要得分四点.如果手和切牌之间的所有五张牌都匹配,则玩家有权获得五分.(Flushes are trivial to count. Iterate over the cards of the hand, and as soon as the suit of a card is not equal to the suit of the first card in the hand, exit, and score the player 0. If you pass the end of the loop, it means all of the suits are the same, and the player is entitled to the count for the flush. Remember that all five cards must match to score flush points in the kitty, but only the four hand cards are required to score four points. If all five cards between the hand and the cut card match, the player is entitled to five points.) 计数点也很容易.(Counting nobs is similarly easy.)
for (Int32 i = 0; i < hand.Length; i++)
if ((hand[i].Value == 'J') && (hand[i].Suit == cutCard.Suit))
AddNobsToScore(hand[i], cutCard);
我发现最难得分的牌是奔跑.一轮是按顺序排列的一组牌,不一定是相同的花色,可以长3\4或5张牌.一只给定的牌中可以有多个奔跑.考虑以下手:(I found the hardest sets of cards to score were the runs. A run is a set of cards in order, not necessarily of the same suit, and can be 3, 4, or 5 cards long. There can be more than one run in a given hand. Consider the following hand:)
图像5. 3的四倍运行.(Image 5. Quadruple run of 3.)有四次运行:(There are four runs:)
- 黑桃A,红心2,俱乐部3(Ace of Spades, 2 of Hearts, 3 of Clubs)
- 黑桃王牌,2俱乐部,3俱乐部(Ace of Spades, 2 of Clubs, 3 of Clubs)
- 红桃王牌,红桃2,俱乐部3(Ace of Hearts, 2 of Hearts, 3 of Clubs)
- 红桃王牌,2俱乐部,3俱乐部(Ace of Hearts, 2 of Clubs, 3 of Clubs)
另外,请考虑以下两轮操作:(Also, consider the following hand of two runs:)
图6.重复两次.(Image 6. Double run of 4.)我只想计算两次四次(黑桃A,2俱乐部,3俱乐部,4钻石和红桃A,2俱乐部,3俱乐部,4钻石),而不是任何一个运行3.(I want to count only the two runs of 4 (Ace of Spades, 2 of Clubs, 3 of Clubs, 4 of Diamonds, and Ace of Hearts, 2 of Clubs, 3 of Clubs, 4 of Diamonds), and not any of the runs of 3.)
当然,现在对我而言,解决方案是开始寻找最长的运行,并且一旦找到运行,就不要寻找较小的运行.开始寻找5的游程,如果存在一个(只能有一个),则不要寻找4或3的游程.如果找到4的游程,则继续寻找其他4的游程,但是不要寻找t寻找3的运行次数.(The solution, obvious to me now, of course, is to start looking for the longest runs and as soon as a run is found, don’t look for smaller runs. Start looking for a run of 5, and if one exists (there can only be one), don’t look for runs of 4 or 3. If a run of 4 is found, continue looking for other runs of 4, but don’t look for runs of 3.)
实际上,通过选择适当的子集比较数组并在(Runs are actually found by picking the appropriate subset comparison array, and iterating over the) Ordinal
卡的值.如果两张纸牌彼此相距超过2(或相等),则表示没有奔跑,可以退出奔跑寻找循环而不计分.如果循环完成,则表示所有比较的牌彼此相距1,并且存在一次跑动.对跑步进行评分,然后返回以查找更多内容.仅当手先被排序时才起作用,这是最初的动力(values of the cards. If two cards are more than 2 away from each other (or are equal), there’s no run and the run-seeking loop can be exited without scoring. If the loop completes, it means all the cards compared are 1 away from each other and a run exists. Score the run, and go back to look for more. This can only work if the hand has been sorted first, which was the original impetus to have) Card
实行(implement) IComparable
.(.)
代码(The Code)
现在我们有了(Now that we have) Card
对象以及我们需要的所有算法,我们可以查看它们的得分组.的(objects and all the algorithms we need, we can look at scoring groups of them. The) Hand
全班只有一个(class has a single) static
方法,(method,) Count()
.它接受一个数组(. It accepts an array of) Card
对象,单个(objects, a single) Card
代表切卡的对象,指向(object representing the cut card, a pointer to a) List<>
的(of) ScoreSet
对象(稍后会详细介绍),以及(objects (more on this later), and a) bool
指定要评估的手是小猫还是普通手.(that specifies whether the hand to be evaluated is a kitty or a regular hand.)
的(The) ScoreSet
类包含一个(class contains a) string
Name
得分事件的属性(15,对,跑等),(property of the scoring event (15, pair, run, etc.), the) Count
的事件,以及一系列(of the event, and an array of) Card
构成得分事件的对象.这些对象累积在(objects that constitute the scoring event. These objects are accumulated in the) List<>
因此哪些卡贡献了得分的哪一部分可以报告给玩家.(so which cards contributed to which part of their score can be reported back to the player.)
我包括一小(I included a small) Deck
对象来管理挑选随机卡片.它公开了两种方法,(object to manage picking random cards. It exposes two methods,) Shuffle()
和(, and) NextCard()
,它返回一个(, which returns an) Int32
代表下一张取消交易的卡片的卡片索引(0-51).(representing the card index (0 - 51) of the next un-dealt card.)
演示应用(The Demo App)
该演示应用程序是一个控制台应用程序,将所有这些概念组合在一起.如果您没有向其传递任何命令行参数,它将生成随机的一手牌,挑选一张随机的"过牌"牌,并对该手牌得分.这是通过以下代码完成的:(The demo application is a console app that puts all of these concepts together. If you don’t pass it any command-line parameters, it generates a random hand, picks a random “cut” card, and scores the hand. This is done with this code:)
List<ScoreSet> scoringPlays = new List<ScoreSet>();
Card[] hand = new Card[4];
Deck deck = new Deck();
for (Int32 i = 0; i < 4; i++)
card[i] = new Card(deck.NextCard());
Card cutCard = new Card(deck.NextCard());
Int32 score = Hand.Count(hand, cutCard, scoringPlays, false); // Not a kitty
如果您确实传递了命令行参数,则可以指定一个得分手.像这样使用它:(If you do pass command-line parameters, you can specify a hand for it to score. Use it like this:)
criblibhandcounter.exe Card1 Card2 Card3 Card4 CutCard [true]
这些卡指定为NS,其中N是(The cards are specified as NS, where N is the) Name
字符,S是(character and S is the) Suit
字符.如果您指定第六个可选参数,则为"(character. If you specify the sixth optional parameter, and it is “) true
“,您提供的那只手将被计为婴儿床.(”, the hand you supply will be scored as a crib.)
演示应用程序中的其余代码只是稍微修饰了输出.(The rest of the code in the demo app just prettifies the output a bit.)
冥想(Deep Thought)
当MSN可用时,我非常喜欢在MSN上玩婴儿床,最终玩了2000多个游戏.并不是很着迷,而是在一周中的一个晚上每晚进行两到三场比赛,而随着时间的推移,周末的每个晚上都会增加几场比赛.当然,所有这些都是在我儿子到达之前发生的.(I enjoyed playing crib on MSN a great deal when it was available, and ended up playing over 2000 games. Not obsessively, but two or three games a night during the week, and a couple more each night of the weekend adds up over time. All this happened before my son arrived, of course.) 关于玩MSN垃圾令我烦恼的一件事是,您的排名可能会因输掉您本不想赢的游戏而受到负面影响.有一些近距离比赛我会因为太过激进而在接近尾声时输掉比赛,但还有其他一些比赛我输掉了,因为我会在游戏中得到四张” 19"手,而我的对手会得到12s和16s.我会觉得很臭,我的评分会降低,这并不能真正代表我在比赛中的技能.(One of the things that annoyed me about playing MSN cribbage was the fact that your ranking could be negatively affected by losing games that you couldn’t hope to win. There were some close games that I’d lose due to being too aggressive with pegging near the end, but there were other games that I’d lose because I’d get four “19” hands in the game, while my opponent would get 12s and 16s. I’d get skunked, my rating would take a nose-dive, and it didn’t really represent my skill at the game.) 如果有人在考虑编写自己的婴儿床游戏,我想在这里加两分钱.我想介绍"标准游戏"的概念,其中游戏的手不是随机的而是固定的.这将使排名引擎能够比较玩家玩给定牌组的情况.某些标准游戏本可以拥有一组非常不错的纸牌,而一组不良纸牌却是这样,但是如果预先确定了不良手牌的玩家比其他玩过相同标准手牌的玩家多赢得了2分,即使他们输掉了比赛,也会对他们的排名产生积极的影响.(If anyone out there is contemplating writing their own crib game, I’d like to add two cents here. I’d like to introduce the concept of “standard games”, where the hands for the game are not random but fixed. It would allow the ranking engine to compare how well players play given sets of cards. Some standard games would have a really good set of cards up against a set of poor cards, but if the player with the pre-destined poor hands managed to eke out two points more than other players who’ve played the same standard hand, it would reflect positively in their ranking, even if they lost that game.) 玩家甚至可以在不玩游戏的情况下看到排名的变化.如果该玩家是第一个玩标准游戏的玩家,而随后的10个玩家对此做得更糟,则第一个玩家的排名应该会上升.玩家的赢/输记录将是静态的,但如果不采取任何措施,他们的"标准游戏"排名可能会波动.(Players could even see their ranking change without playing games. If the player was the first one to play a standard game, and ten subsequent players do worse with it, the first player’s ranking should rise. A player’s Win/Loss record would be static, but their “Standard Game” ranking could fluctuate without their action.) 最初,我认为数据库中必须填充大量标准游戏(这样玩家就无法识别他们已经玩过的标准手),但是生成标准游戏会更加有效最初是随机游戏.跟踪哪个玩家玩了哪些标准游戏是很简单的,如果其中一个玩家玩了所有标准游戏,引擎将为他们生成一个新的随机游戏(随后将其添加到标准游戏目录中)对于其他玩家).(Initially, I thought the database would have to be populated with a very large number of standard games (so a player wouldn’t be able to recognize a standard hand they’ve already played), but it would be far more efficient to generate standard games from random ones played initially. It would be trivial to track which players had played which standard games, and if either of the players had played all of the standard games, the engine would generate a new random game for them (which would subsequently be added to the catalog of standard games for other players).) 无论如何,如果有统计,数学或精算博士学位的候选人正在寻找论文,我很乐意与您合作.(Anyway, if there are any statistics, math, or actuarial PhD candidates looking for a thesis, I’d be happy to work with you on this.)
结论(Conclusion)
自从我9岁或10岁时父亲教我以来,我就一直在玩纸牌.大约30年前,我真的很喜欢解构该图书馆的评分程序.可能还会有更多与婴儿床有关的文章,因此请警告自己.(I have been playing cribbage since my dad taught me when I was 9 or 10; about 30 years ago, and I really enjoyed deconstructing the scoring process for this library. There are probably more crib-related articles on the way, so consider yourself warned.) 拿起甲板并立即尝试!(Pick up a deck and try it today!)
修订记录(Revision History)
- 5(5)日(th)2006年9月-初始修订(September, 2006 - Initial revision)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# C#2.0 WinXP Windows .NET .NET2.0 Visual-Studio VS2005 Dev 新闻 翻译