C#中的更多Texas Holdem分析:第1部分(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/more-texas-holdem-analysis-in-c-part-1-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 13 分钟阅读 - 6180 个词 阅读量 0C#中的更多Texas Holdem分析:第1部分(译文)
原文地址:https://www.codeproject.com/Articles/19091/More-Texas-Holdem-Analysis-in-C-Part-1
原文作者:Keith Rule
译文由本站 robot-v1.0 翻译
前言
Using C# to do sophisticated analysis of Texas Holdem
使用C#对Texas Holdem进行复杂的分析
介绍(Introduction)
2005年12月,我发表了(In December 2005, I published*) “Fast, Texas Holdem Hand Evaluation and Analysis”*)“快速的德州扑克手评估和分析”( 这是我的第一篇德州扑克分析文章.从那时起,我对代码进行了很多增强和扩展.我也有几个比较精通扑克分析的人提供他们的建议.扑克库的此更新提供了许多新的独特功能,这些功能使对Texas Holdem的分析更加容易.在本文中,您将找到以下主题:(which was my first Texas Holdem Analysis article. Since then, I’ve done a lot of enhancements and extensions to the code. I’ve also had several folks more skilled in poker analysis offer their advice. This update to the poker library offers many new and unique features that make analysis of Texas Holdem much easier. In this article, you will find the following topics covered:)
-
掌上查询语言-(Pocket Query Language -)这提供了一组简单明了的方法,使您可以在不重写代码的情况下尝试不同的口袋手组合.(This offers a straightforward set of methods that allow you experiment with different pocket hand combinations without rewriting your code.)
-
抽奖方法(Outs and draw methods)-确定抽奖是分析情况的关键.本节讨论用于执行此操作的技术.(- Determining outs and draws is key to analyzing a situation. This section discusses techniques for doing just that.) 第2部分(Part 2) 将涵盖以下主题:(will cover these topics:)
-
蒙特卡洛分析(Monte Carlo analysis)-有时快速估算就足够了.蒙特卡洛分析可以在很短的时间内做出高质量的估计.(- Sometimes quick estimates are good enough. Monte Carlo Analysis makes it possible to make high quality estimates in a very short time.)
-
多人手牌分析-(Multi-player hand analysis -)通常,您正在与多个玩家对战.这是在合理的时间内计算对多个玩家的获胜几率的方法.(More often than not, you are playing against multiple players. Here’s how to calculate win odds against multiple players in a reasonable amount of time.)
-
多核支持(Multiple core support)-使用多个内核来减少计算时间.添加核时,使用此技术的提速几乎是线性的.(- Using multiple cores to decrease calculation time. The speed ups using this technique are nearly linear when adding cores.)
掌上查询语言(Pocket Query Language)
如果您正在阅读本文,我相信您已经读过一两本关于Holdem的书.我在阅读扑克书籍时注意到的一件事是,有一个(If you are reading this article, I’m sure you’ve read a book or two on Holdem. One of the things I’ve noticed while reading poker books is that there is a) 事实标准(de facto standard) 用于描述口袋卡.大多数书籍使用以下变体来描述口袋手.(for describing pocket cards. Most books use some variant of the following to describe pocket hands.) |口袋手描述语言(Pocket Hand Description Language)
Example |
交流电(Ac Kd) |
AK,78s(AKs, 78s) |
QJ,T8(QJ, T8) |
Kx,Ax(Kx, Ax) |
大多数解析口袋手的字符串表示形式的扑克软件仅支持事实上的标准中的第一项.我实现了更丰富的查询语言.我支持所有常见的扑克书语法以及一些扩展.(Most poker software that parses string representations of pocket hands only supports the first item in the de facto standard. I’ve implemented a much richer query language. I support all of the common poker book syntax, plus some extensions.) |扩展口袋手描述语言(The Extended Pocket Hand Description Language)
Example |
Group1 |
Suited |
Offsuit |
Connected |
Gap1 |
Gap2 |
Gap3 |
我还添加了一些运算符.(I’ve also added some operators.) |口袋手形描述语言基本运算符(Pocket Hand Description Language Basic Operators)
Example |
Group2ToGroup5 |
Group3AndOffset |
Group1OrGroup3 |
NotOffset |
(AK * |
为什么使用袖珍查询语言(Why a Pocket Query Language)
我做过很多次的事情之一就是编写代码来分析特定的比赛.例如,您是否曾经想过拥有合适的连接器与不适合的连接器相比有什么优势?我猜您可能已经想知道了,但是仅仅受虐狂不足以编写代码.另一方面,我有足够的受虐狂来为许多对决编写代码.一段时间后,我认为我已经受够了,并编写了一种查询语言,这样我就可以编写一次匹配分析,然后只需放入查询字符串即可.以下是使用查询字符串而不是硬编码匹配的结果示例.哦,合适的连接器大约有5%的优势.(One of the things I’ve done too many times has been to write code to analyze specific match-ups. For example, have you ever wondered what the advantage is to having a suited connector versus a non-suited connector? I’d guess that you’ve probably wondered, but weren’t enough of a masochist to write the code. On the other hand, I am enough of a masochist to write the code for many, many match-ups. After awhile, I decided I’d had enough of that and wrote a query language so that I could write my match-up analysis once and just put in query strings. The following is an example of the result of using query strings rather than hard coded match-ups. Oh, and there is about a 5% advantage for suited connectors.)
在代码中使用Pocket Queries(Using Pocket Queries in code)
我试图使编写利用Pocket Queries的分析代码变得微不足道.这是一个例子.(I’ve attempted to make it trivial to write analysis code that utilized Pocket Queries. Here’s an example.)
using System;
using System.Collections.Generic;
using System.Text;
using HoldemHand;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// A Pocket Query Returns an array of all
// hands that meet the criterion.
ulong[] player1 = PocketHands.Query("Connected Suited");
ulong[] player2 = PocketHands.Query("Connected Offsuit");
// Holds stats
long player1Wins = 0, player2Wins = 0,
ties = 0, count = 0;
// Iterate through 10000 trials.
for (int trials = 0; trials < 10000; trials++)
{
// Pick a random pocket hand out of
// player1's query set
ulong player1Mask = Hand.RandomHand(player1, 0UL, 2);
// Pick a random pocket hand for player2
ulong player2Mask = Hand.RandomHand(player2, player1Mask, 2);
// Pick a random board
ulong boardMask
= Hand.RandomHand(player1Mask | player2Mask, 5);
// Create a hand value for each player
uint player1HandValue =
Hand.Evaluate(boardMask | player1Mask, 7);
uint player2HandValue =
Hand.Evaluate(boardMask | player2Mask, 7);
// Calculate Winners
if (player1HandValue > player2HandValue)
{
player1Wins++;
}
else if (player1HandValue < player2HandValue)
{
player2Wins++;
}
else
{
ties++;
}
count++;
}
// Print results
Console.WriteLine("Player1: {0:0.0}%",
(player1Wins + ties / 2.0) / ((double)count) * 100.0);
Console.WriteLine("Player2: {0:0.0}%",
(player2Wins + ties / 2.0) / ((double)count) * 100.0);
}
}
}
注意,我已经向Holdem库添加了一个新类.叫做(Notice that I’ve added a new class to the Holdem library. It’s called) PocketHands
.以下是一些有关如何使用此类的简单示例:(. Here are some simple examples of how to use this class:)
使用此类的最简单方法是遍历指定Pocket Query的所有可能的Pocket Hand.(The simplest way to use this class is to iterate through all possible pocket hands for the specified Pocket Query.)
// This will iterate through all the possible "connected suited" pocket hands
foreach (ulong pocketmask in PocketHands.Query("Connected Suited"))
{
// Insert calculation here.
}
使用此类的另一种方法是在给定特定手部匹配的情况下遍历Pocket Query.(Another way to use this class is to iterate through a Pocket Query given a specific hand match-up.)
// Looks at an AKs match up (specifically As Ks) against all possible
// opponents hands that are connected and suited.
ulong mask = Hand.Evaluate("As Ks"); // AKs
foreach (ulong oppmask in PocketHands.Query("Connected Suited", mask))
{
// Insert calculation here.
}
本示例通过两个特定的Pocket Query匹配详尽地循环.(This example loops exhaustively through two specific Pocket Query match-ups.)
// Iterates through all possible "Connected Suited" versus
// "Connected Offsuit" match ups.
foreach (ulong playermask in PocketHands.Query("Connected Suited"))
{
foreach (ulong oppmask in PocketHands.Query(
"Connected Offsuit", playermask))
{
foreach (ulong board in Hand.Hands(0UL, playermask | oppmask, 5))
{
// Insert Calculation Here
}
}
}
在对对随机样本时,也可以使用口袋查询.(It’s also possible to use pocket queries while doing random samples of match-ups.)
// Randomly selects 100000 possible hands when player starts with a
// suited connector
ulong[] masks = PocketHands.Query("Connected Suited");
for (int trials = 0; trials < 100000; trials++)
{
// Select a random player hand from the list of possible
// Connected Suited hands.
ulong randomPlayerHandMask = Hand.RandomHand(masks, 0UL, 2);
// Get a random opponent hand
ulong randomOpponentHandMask = Hand.RandomHand(randomPlayerHandMask, 2);
// Get a random board
ulong boardMask =
Hand.RandomHand(randomPlayerHandMask | randomOpponentHandMask, 5);
// Insert evaluation here
}
抽签(Outs and draws)
大多数德州扑克玩家知道什么(Most Hold’em players know what) 外出(outs) 是.根据维基百科:(are. According to Wikipedia:) [A] n是任何看不见的纸牌,如果被抽出,将会使玩家的手牌提高到可能会赢的手([A]n out is any unseen card that, if drawn, will improve a player’s hand to one that is likely to win) 对于程序员来说,问题是:“这个定义足以编写一个返回缺卡的函数吗?“让我们看一下Wikipedia提出的两个关键点.他们是:(The question for the programmer is, “Is this definition sufficient to write a function that returns the cards that are outs?” Let’s look at the two key points made by Wikipedia. They are:)
- 手必须提高.(The hand must improve.)
- 新手可能会赢.(The new hand is likely to win.) 让我们从编写满足第一个条件的函数开始.(Let’s start by writing a function that meets the first criterion.)
using System;
using HoldemHand;
// A first try at calculating outs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string pocket = "As Ac";
string board = "Kd 8h 9c";
// Calcuate the outs
ulong outsmask =
Outs(Hand.ParseHand(pocket), Hand.ParseHand(board));
Console.WriteLine("[{0}] {1} : Outs Count {2}",
pocket, board, Hand.BitCount(outsmask));
// List the cards
foreach (string card in Hand.Cards(outsmask))
{
Console.Write("{0} ", card);
}
Console.WriteLine();
}
// Return a hand mask of the cards that improve our hand
static ulong Outs(ulong pocket, ulong board)
{
ulong retval = 0UL;
ulong hand = pocket board;
// Get original hand value
uint playerOrigHandVal = Hand.Evaluate(hand);
// Look ahead one card
foreach (ulong card in Hand.Hands(0UL, hand, 1))
{
// Get new hand value
uint playerNewHandVal = Hand.Evaluate(hand card);
// If the hand improved then we have an out
if (playerNewHandVal > playerOrigHandVal)
{
// Add card to outs mask
retval = card;
}
}
// return outs as a hand mask
return retval;
}
}
}
将这个起手牌A♠A♣,K♦8♥9♣传递到我们的新方法中将返回以下结果:(Passing this starting hand A♠ A♣, K♦ 8♥ 9♣ into our new method returns the following outs:)
-
K♠,9♠,9♥,8♠,K♥,9♦,8♦,K♣,8♣-两对(K♠, 9♠, 9♥, 8♠, K♥, 9♦, 8♦, K♣, 8♣ - Two pair)
-
A♥,A♦-旅行(A♥, A♦ - Trips)
-
Q♠,J♠,T♠,Q♥,J♥,T♥,Q♦,J♦,T♦,Q♣,J♣,T♣-改善踢球(Q♠, J♠, T♠, Q♥, J♥, T♥, Q♦, J♦, T♦, Q♣, J♣, T♣ - Improves Kicker) 我认为大多数人都会同意,将踢球者的身高调整在这里可能无济于事.我认为大多数人也会同意改善董事会也无济于事.因此,我们再添加两个规则:(I think most people would agree that super-sizing your kicker probably doesn’t help much here. I think most people would also agree that improving the board doesn’t help either. So, let’s add two more rules:)
-
手必须提高.(The hand must improve.)
-
新手的改进必须比改进的踢球手更好.(The new hand improvement must be better than an improved kicker.)
-
新组合的手必须比仅董事会强.(The new combined hand must be stronger than the just the board.)
-
新手可能会赢.(The new hand is likely to win.) 下面的示例处理这些新规则,并允许添加对手的牌.(The following example handles these new rules and allows opponent hands to be added.)
using System;
using HoldemHand;
// A first try at calculating outs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string pocket = "As Ac";
string board = "Kd 8h 9c";
// Calcuate the outs
ulong outsmask =
Outs(Hand.ParseHand(pocket), Hand.ParseHand(board));
Console.WriteLine("[{0}] {1} : Outs Count {2}",
pocket, board, Hand.BitCount(outsmask));
// List the cards
foreach (string card in Hand.Cards(outsmask))
{
Console.Write("{0} ", card);
}
Console.WriteLine();
}
// Return a hand mask of the cards that improve our hand
static ulong Outs(
ulong pocket, ulong board, params ulong [] opponents)
{
ulong retval = 0UL;
// Get original hand value
uint playerOrigHandVal = Hand.Evaluate(pocket board);
// Look ahead one card
foreach (ulong card in Hand.Hands(0UL, board pocket, 1))
{
// Get new hand value
uint playerNewHandVal = Hand.Evaluate(pocket board card);
// Get new board value
uint boardHandVal = Hand.Evaluate(board card);
// Is the new hand better than the old one?
bool handImproved =
playerNewHandVal > playerOrigHandVal &&
Hand.HandType(playerNewHandVal) > Hand.HandType(
playerOrigHandVal);
// This compare ensures we move up in hand type.
bool handStrongerThanBoard =
Hand.HandType(playerNewHandVal) > Hand.HandType(
boardHandVal);
// Check against opponents cards
bool handBeatAllOpponents = true;
if (handImproved && handStrongerThanBoard &&
opponents != null && opponents.Length > 0)
{
foreach (ulong opponent in opponents)
{
uint opponentHandVal =
Hand.Evaluate(opponent board card);
if (opponentHandVal > playerNewHandVal)
{
handBeatAllOpponents = false;
break;
}
}
}
// If the hand improved then we have an out
if (handImproved && handStrongerThanBoard &&
handBeatAllOpponents)
{
// Add card to outs mask
retval = card;
}
}
// return outs as a hand mask
return retval;
}
}
}
结果计算的问题是,您通常不知道自己所面对的对手牌.这使得该计算有点主观.我已经与不同的人讨论了很多.与Matt Baker进行的讨论是更有趣的讨论之一.他重写了我的出局功能(如上所示),以包括一种试探法,该试探法旨在更准确地说明对手的牌.我不会在这里讨论,但是您可以使用他的代码.他慷慨地提供(The problem with outs calculation is that you often don’t know the opponent cards you are up against. That makes this calculation a bit subjective. I’ve had many discussions with different folks about this. One of the more interesting discussions was with Matt Baker. He rewrote my outs function (shown above) to include a heuristic that tries to more accurately account for opponent cards. I won’t go into that here, but you can use his code. He generously provided) OutsDiscounted
和(and) OutsMaskDiscounted
.(.)
static int OutsDiscounted(ulong player, ulong board, params ulong[] opponents)
的(The) OutsDiscounted
函数返回指定玩家的口袋手,当前局面和对手的口袋手的输球数.(function returns the number of outs for the specified player’s pocket hand, the current board and optionally the opponent pocket hands.)
ulong OutsMaskDiscounted(ulong player, ulong board, params ulong[] opponents)
的(The) OutsMaskDiscounted
函数返回所有缺牌的卡片掩码.您可以通过调用将该掩码转换为字符串(function returns a card mask of all of the cards that are outs. You can turn this mask into a string by calling) MaskToString()
.(.)
抽签变化(Draw variation)
我有好几个人联系我,讨论其他功能,这些功能专注于特定的支出计算.这些称为"绘制"计算.韦斯利`坦西(Wesley Tansey)请求了一些绘制函数的变体,并愿意为我进行测试.结果是以下功能集.(I’ve had several folks contact me about other functions that focus on specific outs calculations. These are referred to as “draw” calculations. Wesley Tansey requested several variants of draw functions and offered to test them for me. The result is the following set of functions.)
StraightDrawCount(StraightDrawCount)
的(The) StraightDrawCount
方法返回玩家,棋盘和死卡配置可能发生的平局次数.它还过滤结果,因此只计算玩家的手牌提升.(method returns the number of straight draws that are possible for the player, board and dead card configuration. It also filters the results so only player hand improvements are counted.)
public static int StraightDrawCount(ulong player, ulong board, ulong dead)
IsOpenEndedStraightDraw(IsOpenEndedStraightDraw)
的(The) IsOpenEndedStraightDraw
如果组合蒙版是开放式的平头绘制,则函数返回true.此方法仅考虑改善球员面具的直率可能性.(function returns true if the combined mask is an open-ended straight draw. Only straight possibilities that improve the player’s mask are considered in this method.)
public static bool IsOpenEndedStraightDraw(ulong pocket,
ulong board, ulong dead)
IsGutShotStraightDraw(IsGutShotStraightDraw)
方法(The method) IsGutShotStraightDraw
如果组合的卡片包含直击平局,则返回true.(returns true if the combined cards contain a gut shot straight draw.)
public static bool IsGutShotStraightDraw(ulong pocket,
ulong board, ulong dead)
IsStraightDraw(IsStraightDraw)
方法(The method) IsStraightDraw
如果组合的卡包含平局,则返回true.(returns true if the combined cards contain a straight draw.)
public static bool IsStraightDraw(ulong pocket, ulong board, ulong dead)
IsOpenEndedStraightDraw(IsOpenEndedStraightDraw)
方法(The method) IsOpenEndedStraightDraw
如果组合的卡牌包含不限次数的平局,则返回true.(returns true if the combined cards contain an open-ended straight draw.)
public static bool IsOpenEndedStraightDraw(ulong pocket,
ulong board, ulong dead)
FlushDrawCount(FlushDrawCount)
此方法计算与另外一张已抽牌同花的手的数量.但是,仅考虑改善板面的冲洗手.(This method counts the number of hands that are a flush with one more drawn card. However, only flush hands that improve the board are considered.)
public static int FlushDrawCount(ulong player, ulong board, ulong dead)
IsFlushDraw(IsFlushDraw)
如果有四张相同的花色,则此方法返回true.(This method returns true if there are 4 cards of the same suit.)
public static bool IsFlushDraw(ulong pocket, ulong board, ulong dead)
IsBackdoorFlushDraw(IsBackdoorFlushDraw)
如果有三张相同的花色,则此方法返回true.口袋卡必须在该套装中至少有一张卡.(This method returns true if there are three cards of the same suit. The pocket cards must have at least one card in that suit.)
public static bool IsBackdoorFlushDraw(ulong pocket, ulong board, ulong dead)
抽签数(DrawCount)
此方法返回指定次数可能的抽奖次数(This method returns the number of draws that are possible for the specified) HandType
.它只会返回改善球员面具的计数,而不仅仅是返回棋盘.(. It only returns the counts that improve the player’s mask, rather than just the board.)
public static int DrawCount(ulong player, ulong board,
ulong dead, Hand.HandTypes type)
演示程序(Demo programs)
我在可下载项目中包含了几个演示程序和示例.这是每个演示应用程序的快速摘要:(I’ve included several demo programs and examples in the downloadable project. This is a quick summary of each of the demo applications:)
MultiOddsApp(MultiOddsApp)
该应用程序绘制了几个有趣的值,以使手牌/棋盘场景成为针对1到9个对手的场景.该演示应用程序接受Player Pocket字段的Pocket Query描述.这些值包括:(This application graphs several interesting values to allow hand/board scenarios to be scenarios against 1 through 9 opponents. This demo application accepts Pocket Query descriptions for the Player Pocket field. These values include:)
- 赢得(Win)-这些是给定的指定手牌最终胜出的几率.(- These are the odds that the given specified hand will end up the winning hand.)
- 手力(Hand Strength)-这些是给定的手牌(不包括任何将来发行的卡牌)目前是最佳手牌的几率.(- These are the odds that the given hand – not including any future dealt cards – is currently the best hand.)
- 正电位(Positive Potential)-这些是当前输掉的手将成为赢家的几率.(- These are the odds that a currently losing hand will improve to be a winner.)
- 负电位(Negative Potential)-这些是当前获胜者将成为失败者的几率.(- These are the odds that a current winning hand will become a loser.)
手赔率(Hand Odds)
该应用程序允许使用口袋查询语言输入球员口袋手和对手口袋手的定义以及棋盘定义.对于单个随机对手,将返回产生的赔率.由于这将Pocket Query Language作为Pocket Query Language的输入,因此它很容易尝试各种Pocket Hand方案,而这些方案否则将需要大量编程才能确定答案.(This application allows a player pocket hand and an opponent pocket hand definition – using the Pocket Query Language – to be entered along with a board definition. The resulting odds are returned for a single random opponent. Since this takes the Pocket Query Language as input, it makes it very easy to try all kinds of pocket hand scenarios that would otherwise require a fair amount of programming to determine the answer.)
致谢(Acknowledgements)
- Wesley Tansey-对于确定不同输出变化的功能提供了很多帮助.(Wesley Tansey - For a lot of help with functions determining different variations of outs.)
- 马特
贝克(Matt Baker)-有关讨论和提供(*Matt Baker - For many discussions on outs and for providing the*)
DiscountedOuts` 功能.(functions.) - Scott Turner-用于使用增强功能并提供反馈.(Scott Turner - For using and providing feedback on enhancements.)
- 扑克评估(poker-eval) -我评估引擎的核心.(- The core to my evaluation engine.)
- ZedGraph(ZedGraph) -我在几个用图形表示结果的示例中使用了ZedGraph.(- I’ve used ZedGraph in several of the examples that graph results.)
- 马尔科姆`克劳(Malcolm Crowe) -我用于Pocket Query Language的C#的lexer生成器/解析器生成器工具的作者.(- Author of the lexer generator/parser generator tools for C# that I used for the Pocket Query Language.)
免责声明(Disclaimer)
我很容易花了6个月的时间来编辑和调整代码和示例.我决定在此上花足够的时间,并且拥有大量可用的功能.因此,这是我当前的代码,疣和所有代码.(I could easily have spent another 6 months editing and tweaking the code and examples. I decided that I had spent enough time on this and had a “critical mass” of features available. So, here is my current code, warts and all.) 就此而言,我并没有声称自己是一名出色的扑克玩家,甚至不是一名优秀的扑克玩家.换个说法:可以做的人,不能编码的人.我不建议您在未经独立验证的情况下接受我可能在本文中提供(或暗示提供)的任何扑克建议.(I don’t claim to be a great poker player or even a good one, for that matter. To paraphrase: those who can do, those who can’t code. I don’t recommend taking any poker advice I may have given (or implied to have given) in this article without independent verification.) 我也想鼓励反馈.我敢肯定,扑克程序员似乎有些秘密.从过去一年左右的反馈中,我学到了很多东西.请继续.(I’d also like to encourage feedback. Poker programmers appear to be a somewhat secretive lot, for good reason I’m sure. I’ve learned a lot from the feedback I’ve been given over the last year or so. Please keep it coming.)
历史(History)
2007年5月首次发布(First released May 2007)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# Windows .NET .NET2.0 Visual-Studio Dev 新闻 翻译