An N-Player, Imperfect Information, Zero-sum game

Example

The axle.game.cards package models decks, cards, ranks, suits, and ordering.

Imports

import cats.implicits._
import cats.Order.catsKernelOrderingForOrder

import axle.game.cards.Deck
import axle.game.poker.PokerHand
import axle.game.poker.PokerHandCategory
import axle.string

Define a function that takes the hand size and returns the best 5-card hand

def winnerFromHandSize(handSize: Int) =
  Deck().cards.take(handSize).combinations(5).map(PokerHand(_)).toList.max
// winnerFromHandSize: (handSize: Int)axle.game.poker.PokerHand

string(winnerFromHandSize(7))
// res1: String = 3♢ 3♣ T♡ T♢ A♡

20 simulated 5-card hands made of 7-card hands. Sorted.

val hands = (1 to 20).map(n => winnerFromHandSize(7)).sorted
// hands: scala.collection.immutable.IndexedSeq[axle.game.poker.PokerHand] = Vector(PokerHand(Vector(Card(axle.game.cards.R9$@45a1a38e,axle.game.cards.Spades$@1ab8a921), Card(axle.game.cards.R6$@1d0dcbde,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.Queen$@61f47c63,axle.game.cards.Diamonds$@9197b7c), Card(axle.game.cards.Jack$@62c65875,axle.game.cards.Diamonds$@9197b7c), Card(axle.game.cards.R7$@45c008b5,axle.game.cards.Hearts$@3a8ec01d))), PokerHand(Vector(Card(axle.game.cards.R6$@1d0dcbde,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.R9$@45a1a38e,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.R7$@45c008b5,axle.game.cards.Spades$@1ab8a921), Card(axle.game.cards.King$@6576f383,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.Jack$@62c65875,axle.game.cards.He...

hands.map({ hand => string(hand) + "  " + hand.description }).mkString("\n")
// res2: String =
// 6♣ 7♡ 9♠ J♢ Q♢  high Q high
// 6♣ 7♠ 9♣ J♡ K♣  high K high
// 4♠ 5♢ 8♣ Q♠ K♣  high K high
// 5♣ 7♢ 8♡ 9♠ A♡  high A high
// 3♣ 3♡ 8♢ 9♣ J♡  pair of 3
// T♣ T♡ J♣ Q♠ A♠  pair of T
// 9♠ T♡ T♠ K♡ A♢  pair of T
// T♣ T♠ Q♡ K♠ A♠  pair of T
// 4♠ J♠ J♡ K♢ A♠  pair of J
// T♠ J♢ J♣ K♣ A♡  pair of J
// J♠ J♢ Q♢ K♠ A♣  pair of J
// 4♣ 4♢ 5♢ 5♣ T♣  two pair 5 and 4
// 3♠ 3♡ 6♠ 6♡ A♣  two pair 6 and 3
// 6♣ 6♢ 7♢ 7♣ Q♢  two pair 7 and 6
// 3♣ 3♢ 8♡ 8♠ A♠  two pair 8 and 3
// 6♡ 6♣ 8♠ 8♢ Q♠  two pair 8 and 6
// 6♢ 7♡ 7♣ T♠ T♣  two pair T and 7
// 9♢ 9♣ J♢ J♡ K♠  two pair J and 9
// 2♡ 2♢ J♠ K♢ K♡  two pair K and 2
// 3♠ 3♢ K♠ K♢ A♠  two pair K and 3

Record 1000 simulated hands for each drawn hand size from 5 to 9

val data: IndexedSeq[(PokerHandCategory, Int)] =
  for {
    handSize <- 5 to 9
    trial <- 1 to 1000
  } yield (winnerFromHandSize(handSize).category, handSize)
// data: IndexedSeq[(axle.game.poker.PokerHandCategory, Int)] = Vector((axle.game.poker.High$@74c36f93,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.TwoPair$@73b39f40,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.High$@74c36f93,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle.game.poker.Pair$@7dedf0ad,5), (axle....

BarChartGrouped to visualize the results

import spire.implicits.IntAlgebra
// import spire.implicits.IntAlgebra

import axle.visualize.BarChartGrouped
// import axle.visualize.BarChartGrouped

import axle.visualize.Color._
// import axle.visualize.Color._

val colors = List(black, red, blue, yellow, green)
// colors: List[axle.visualize.Color] = List(Color(0,0,0), Color(255,0,0), Color(0,0,255), Color(255,255,0), Color(0,255,0))

val chart = BarChartGrouped[PokerHandCategory, Int, Int, Map[(PokerHandCategory, Int), Int], String](
  () => data.tally.withDefaultValue(0),
  title = Some("Poker Hands"),
  drawKey = false,
  yAxisLabel = Some("instances of category by hand size (1000 trials each)"),
  colorOf = (cat: PokerHandCategory, handSize: Int) => colors( (handSize - 5) % colors.size),
  hoverOf = (cat: PokerHandCategory, handSize: Int) => Some(s"${string(cat)} from $handSize")
)
// chart: axle.visualize.BarChartGrouped[axle.game.poker.PokerHandCategory,Int,Int,Map[(axle.game.poker.PokerHandCategory, Int),Int],String] = BarChartGrouped(<function0>,false,700,600,50,0.8,20,50,80,Some(Poker Hands),None,Courier New,12,Palatino,20,None,None,Some(instances of category by hand size (1000 trials each)),Some(UnittedQuantity(36.0,UnitOfMeasurement(degree,°,Some(http://en.wikipedia.org/wiki/Degree_(angle))))),<function2>,<function2>,<function2>)

import axle.web._
// import axle.web._

svg(chart, "pokerhands.svg")

poker hands

Texas Hold ‘Em Poker

As a game of “imperfect information”, poker introduces the concept of Information Set.

scala> import axle._
import axle._

scala> import axle.game._
import axle.game._

scala> import axle.game.poker._
import axle.game.poker._

scala> import Strategies._
import Strategies._

scala> val p1 = Player("P1", "Player 1")
p1: axle.game.Player = Player(P1,Player 1)

scala> val p2 = Player("P2", "Player 2")
p2: axle.game.Player = Player(P2,Player 2)

scala> val game = Poker(Vector(
     |   (p1, randomMove, prefixedDisplay("1")(println)),
     |   (p2, randomMove, prefixedDisplay("2")(println))),
     |   prefixedDisplay("D")(println))
game: axle.game.poker.Poker = Poker(Vector((Player(P1,Player 1),<function2>,<function1>), (Player(P2,Player 2),<function2>,<function1>)),<function1>)

scala> play(game)
1> 
1> Texas Hold Em Poker
1> 
1> Example moves:
1> 
1>   check
1>   raise 1
1>   call
1>   fold
1> To: Dealer
1> Current bet: 0
1> Pot: 0
1> Shared: 
1> 
1> P1:  hand -- in for $--, $100 remaining
1> P2:  hand -- in for $--, $100 remaining
2> 
2> Texas Hold Em Poker
2> 
2> Example moves:
2> 
2>   check
2>   raise 1
2>   call
2>   fold
2> To: Dealer
2> Current bet: 0
2> Pot: 0
2> Shared: 
2> 
2> P1:  hand -- in for $--, $100 remaining
2> P2:  hand -- in for $--, $100 remaining
1> Dealer initial deal.
1> To: You
1> Current bet: 2
1> Pot: 3
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $1, $99 remaining
1> P2:  hand -- in for $2, $98 remaining
2> Dealer initial deal.
2> To: Player 1
2> Current bet: 2
2> Pot: 3
2> Shared: 
2> 
2> P1:  hand -- in for $1, $99 remaining
2> P2:  hand 5 2 in for $2, $98 remaining
1> You raises the bet by 25.
1> To: Player 2
1> Current bet: 27
1> Pot: 29
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $27, $73 remaining
1> P2:  hand -- in for $2, $98 remaining
2> Player 1 raises the bet by 25.
2> To: You
2> Current bet: 27
2> Pot: 29
2> Shared: 
2> 
2> P1:  hand -- in for $27, $73 remaining
2> P2:  hand 5 2 in for $2, $98 remaining
1> Player 2 raises the bet by 41.
1> To: You
1> Current bet: 68
1> Pot: 95
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $27, $73 remaining
1> P2:  hand -- in for $68, $32 remaining
2> You raises the bet by 41.
2> To: Player 1
2> Current bet: 68
2> Pot: 95
2> Shared: 
2> 
2> P1:  hand -- in for $27, $73 remaining
2> P2:  hand 5 2 in for $68, $32 remaining
1> You raises the bet by 32.
1> To: Player 2
1> Current bet: 100
1> Pot: 168
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $100, $0 remaining
1> P2:  hand -- in for $68, $32 remaining
2> Player 1 raises the bet by 32.
2> To: You
2> Current bet: 100
2> Pot: 168
2> Shared: 
2> 
2> P1:  hand -- in for $100, $0 remaining
2> P2:  hand 5 2 in for $68, $32 remaining
1> Player 2 folds.
1> To: Dealer
1> Current bet: 100
1> Pot: 168
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $100, $0 remaining
1> P2:  hand -- out, $32 remaining
2> You folds.
2> To: Dealer
2> Current bet: 100
2> Pot: 168
2> Shared: 
2> 
2> P1:  hand -- in for $100, $0 remaining
2> P2:  hand 5 2 out, $32 remaining
1> Dealer pays out.
1> Current bet: 0
1> Pot: 0
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $--, $168 remaining
1> P2:  hand 5 2 in for $--, $32 remaining
2> Dealer pays out.
2> Current bet: 0
2> Pot: 0
2> Shared: 
2> 
2> P1:  hand Q♠ 9 in for $--, $168 remaining
2> P2:  hand 5 2 in for $--, $32 remaining
1> 
1> Current bet: 0
1> Pot: 0
1> Shared: 
1> 
1> P1:  hand Q♠ 9 in for $--, $168 remaining
1> P2:  hand 5 2 in for $--, $32 remaining
1> Winner: Player 1
1> Hand  : not shown
2> 
2> Current bet: 0
2> Pot: 0
2> Shared: 
2> 
2> P1:  hand Q♠ 9 in for $--, $168 remaining
2> P2:  hand 5 2 in for $--, $32 remaining
2> Winner: Player 1
2> Hand  : not shown
res4: axle.game.poker.PokerState = PokerState(<function1>,Deck(Vector(Card(axle.game.cards.R2$@69af958f,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.Jack$@62c65875,axle.game.cards.Hearts$@3a8ec01d), Card(axle.game.cards.R8$@3681d3df,axle.game.cards.Hearts$@3a8ec01d), Card(axle.game.cards.R2$@69af958f,axle.game.cards.Hearts$@3a8ec01d), Card(axle.game.cards.King$@6576f383,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.R6$@1d0dcbde,axle.game.cards.Clubs$@2d36dff0), Card(axle.game.cards.R7$@45c008b5,axle.game.cards.Hearts$@3a8ec01d), Card(axle.game.cards.R5$@e18687,axle.game.cards.Diamonds$@9197b7c), Card(axle.game.cards.Ace$@55ca145a,axle.game.cards.Hearts$@3a8ec01d), Card(axle.game.cards.King$@6576f383,axle.game.cards.Spades$@1ab8a921), Card(axle.game.cards.R10$@6adb32c9...