Skip to content

Instantly share code, notes, and snippets.

@diosmosis
Created January 1, 2012 04:02
Show Gist options
  • Save diosmosis/1546201 to your computer and use it in GitHub Desktop.
Save diosmosis/1546201 to your computer and use it in GitHub Desktop.
Scala Experiment Iteration 2
import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.immutable.HashMap
import scala.collection.mutable.{HashMap => MutableHashMap}
import scala.util.Random
import scala.math.Ordering._
import scala.util.Sorting
/**
* The type passed to RouterActionInvokers.
*
* The 'message' member can be anything.
*/
case class InvokerInfo(callbackName: String, path: Array[String], message: Any)
/**
* Actor used to invoke route actions. When path is successfully routed, its
* callback is executed in an Actor.
*/
class RouterActionInvoker(id: String, callbacks: Map[String, (Array[String], Any) => Unit]) extends Actor {
/**
* Prints out some metadata & calls the necessary callback.
*/
def act() = {
while (true) {
receive {
case InvokerInfo(callbackName, path, message) =>
callbacks(callbackName)(path, message)
}
}
}
}
/** Represents a segment in a router's tree of valid paths. Describes exactly what
* path segments are allowed to come after this one.
*/
class RouteTreeNode(val segment:String) {
/** The unique name of the callback associated with this node.
*
* If this member is not null, then the path that leads to this node is a
* valid path.
*/
var callbackName:String = null
/** The child nodes representing possible paths this segment can lead to. */
private val children:MutableHashMap[String, RouteTreeNode] = new MutableHashMap()
/** Adds the child to this node's list of children.
*
* @param child The child node to add. If there already is a child node w/ the
* same segment as this node, it is overwritten.
* @return returns the child node for convenience.
*/
def addChild(child:RouteTreeNode):RouteTreeNode = {
children(child.segment) = child
return child
}
/** Returns the child node with the specified segment, or null if there is none.
*
* @param segment The path segment.
* @return The child node associated with segment or null.
*/
def getChild(segment:String):RouteTreeNode = {
return children.getOrElse(segment, null)
}
}
/** The default actor picking algorithm used by Router.
*
* Cycles through the list of actors so each new route callback is handled by
* the next actor.
*/
class ActorCarousel(actorCount:Int) extends Function1[String, Int] {
private var currentActor = 0
def apply(callbackName:String):Int = {
val result = currentActor
currentActor = (currentActor + 1) % actorCount
return result
}
}
/** Maintains and traverses a tree of "/my/path/to/something"-like paths.
*
* A Router will associate URL paths, like /this/is/a/path, with callbacks and
* attempt to invoke these callbacks when given an arbitrary path.
*
* Router will allow you to use wildcards in place for path segments. For
* example, /this/:will/match will match both "/this/will/match" and
* "/this/abc/match".
*
* The initialize method must be called before any routing is done and after
* all routes are added.
*/
class Router(actorCount:Int) extends Actor {
case class RouterRequest(path:String, data:Any)
/** The root node of the Router's route tree. Holds every valid path & the
* callbacks associated with them.
*/
private var routeTree:RouteTreeNode = new RouteTreeNode("")
/** The callback invoked when routing fails. */
private var onError:(Array[String]) => Unit = null
/** Maps every callback with the string route its associated with. */
private val allCallbacks:MutableHashMap[String, (Array[String], Any) => Unit] = new MutableHashMap()
/** The array of actors used to invoke callbacks. */
private var actors:Array[Actor] = null
/** The function used to decide which actor to use when handling a route.
* Takes the route path as an argument.
*/
private var actorPicker:(String) => Int = new ActorCarousel(actorCount)
/** Method to call to finish two-phase construction. Must be called before
* doing any routing.
*/
def initialize() = {
actors = new Array[Actor](actorCount)
for (i <- 0 until actorCount) {
val actor = new RouterActionInvoker(i.toString(), allCallbacks.toMap)
actor.start()
actors(i) = actor
}
this.start()
}
def act() {
while (true) {
receive {
case RouterRequest(path, data) =>
routeImpl(path, data)
}
}
}
private def routeImpl(path:String, data:Any):Unit = {
// get the path segments & remove the empty segments
val segments = path.split("/").filter((s) => s.length > 0)
// travel through the route tree, segment by segment
var node = routeTree
for (segment <- segments) {
// look for a child node that matches the segment exactly
var child = node.getChild(segment)
// no match? try looking for a wildcard
if (child == null) {
child = node.getChild("*")
}
// no match? invoke onError
if (child == null) {
if (onError != null) onError(segments)
return
}
node = child
}
// if the specific node has no callback, it is not a valid path end
if (node.callbackName == null) {
if (onError != null) onError(segments)
return
}
// pick the actor & send it a message
val actor = actors(actorPicker(node.callbackName))
actor ! new InvokerInfo(node.callbackName, segments, data)
}
/** Sends a message to this actor that invokes the callback associated with the
* given path, or the onError callback if the path is invalid.
*
* @param path The path to route.
* @param data The data to send to the callback.
* @return True if the route was successful, false if otherwise.
*/
def route(path: String, data: Any) = {
if (actors == null || (actors contains null)) {
throw new IllegalStateException("Router must be initialized first!");
}
this ! new RouterRequest(path, data)
}
/** Associates the supplied path with the supplied callback.
*
* @param path The URL path to associate with. Must be of the format: "/a/sample/path".
* Can use wildcards in the format of "/a/path/:wildcard-name".
* @param callback The callback to run when a matched path is routed. This callback
* will be supplied the path segments when invoked.
* @return The Router instance for convenience.
*/
def addRoute(path: String, callback: (Array[String], Any) => Unit):Router = {
if (actors != null) {
throw new IllegalStateException("New routes cannot be added after the initialize method has been called!")
}
// get the path segments & remove the empty segments
val segments = path.split("/").filter((s) => s.length > 0)
// travel the route tree and add missing nodes as they come up
var node = routeTree
for (segment <- segments) {
var realSegment = segment
// if segment is a wildcard, replace it with the '*' value. right now,
// we don't care about the wildcard's name
if (realSegment.startsWith(":")) {
realSegment = "*"
}
val child = node.getChild(realSegment)
if (child == null) {
node = node.addChild(new RouteTreeNode(realSegment))
} else {
node = child
}
}
node.callbackName = path
allCallbacks(path) = callback
return this
}
/** Sets the callback that is run when routing fails.
*
* @param callback The callback.
* @return The Router instance for convenience.
*/
def setOnError(callback: (Array[String]) => Unit):Router = {
onError = callback
return this
}
/** Sets the function used to decide which actor to use when invoking a
* route action.
*/
def setActorPicker(picker: (String) => Int):Router = {
if (picker == null) {
throw new IllegalArgumentException("picker cannot be null")
}
actorPicker = picker
return this
}
}
trait Player {
def name:String
def id:String
}
/** Poker state */
object PokerCards {
val CardsPerSuit = 13
def cardTypeOf(card:Int):Int = {
return card / CardsPerSuit
}
def cardNumOf(card:Int):Int = {
return card % CardsPerSuit
}
def cardToString(card:Int):String = {
val cardType = cardTypeOf(card)
val cardNum = cardNumOf(card)
var result = ""
cardNum match {
case 9 => result = "J"
case 10 => result = "Q"
case 11 => result = "K"
case 12 => result = "A"
case _ => result = (cardNum + 2).toString()
}
cardType match {
case 0 => result += "H"
case 1 => result += "D"
case 2 => result += "C"
case 3 => result += "S"
}
return result
}
}
class PokerDeck {
private val rand:Random = new Random()
private var cards:Array[Int] = Array.range(0, 52) // hearts, diamonds, clubs, spades
private var topCardIdx = 0
def shuffle() = {
cards = rand.shuffle(cards.toList).toArray
topCardIdx = 0
}
def nextCard():Int = {
val result = cards(topCardIdx)
topCardIdx += 1
return result
}
}
object PokerHandType extends Enumeration {
val Nothing, Pair, TwoPair, ThreeOfAKind, Straight, Flush, FullHouse, FourOfAKind, StraightFlush = Value
}
class PokerHand(first:Int = -1, second:Int = -1, third:Int = -1, fourth:Int = -1, fifth:Int = -1) {
val cards = Array(first, second, third, fourth, fifth)
override def toString():String = {
return cards.toList.map((c) => if (c >= 0) PokerCards.cardToString(c) else "--").reduceLeft(_ + ", " + _)
}
def value:Tuple6[Int, Int, Int, Int, Int, Int] = {
val sorted = cards.toList.sortBy((card) => PokerCards.cardNumOf(card))
// check for flush (checks if all cards have same type)
val firstCardType = PokerCards.cardTypeOf(sorted(0))
val hasFlush = sorted.count(PokerCards.cardTypeOf(_) == firstCardType) == sorted.length
// check for straight (checks if deltas all == -1)
val hasStraight = (sorted, sorted.drop(1)).zipped.map(_ - _).count(_ == -1) == sorted.length - 1
var handType = PokerHandType.Nothing
(hasFlush, hasStraight) match {
case (true, true) =>
handType = PokerHandType.StraightFlush
case (true, false) =>
handType = PokerHandType.Flush
case (false, true) =>
handType = PokerHandType.Straight
case (false, false) =>
// count the occurance of each card in the hand
val cardCounts = Array.fill[Int](PokerCards.CardsPerSuit)(0)
sorted.foreach((i) => cardCounts(PokerCards.cardNumOf(i)) += 1)
Sorting.stableSort(cardCounts, (lhs:Int, rhs:Int) => lhs > rhs)
// match the card occurances w/ tuples that represent the hand value.
cardCounts match {
// four of a kind
case Array(4, _*) =>
handType = PokerHandType.FourOfAKind
// full house
case Array(3, 2, _*) =>
handType = PokerHandType.FullHouse
// three of a kind
case Array(3, _*) =>
handType = PokerHandType.ThreeOfAKind
// two pair
case Array(2, 2, _*) =>
handType = PokerHandType.TwoPair
// one pair
case Array(2, _*) =>
handType = PokerHandType.Pair
// nada
case _ =>
handType = PokerHandType.Nothing
}
}
// return a tuple that describes the value of the hand
return (handType.id, sorted(4), sorted(3), sorted(2), sorted(1), sorted(0))
}
}
class PublicPokerState(
val visibleHands:Map[String, PokerHand],
val cash:Map[String, Int],
val anteAmount:Int,
val betAmount:Int,
val totalPot:Int,
val currentPlayer:String,
val nextPlayer:String) {
def canRaise(playerID:String, amount:Int):Boolean = {
return cash(playerID) >= betAmount + amount
}
def canCall(playerID:String):Boolean = {
return cash(playerID) >= betAmount
}
}
class PokerState(players:List[Player], cashToStart:Int = 200) {
private val names:MutableHashMap[String, String] =
MutableHashMap[String, String]() ++ players.map((p) => (p.id, p.name)).toMap
private val hands:MutableHashMap[String, PokerHand] =
MutableHashMap[String, PokerHand]() ++ players.map((p) => (p.id, new PokerHand())).toMap
private val cash:MutableHashMap[String, Int] =
MutableHashMap[String, Int]() ++ players.map((p) => (p.id, cashToStart)).toMap
private var anteAmount:Int = 10
private var betAmount:Int = 0
private var totalPot:Int = 0
private var currentPlayerIdx:Option[Int] = None
private var dealerIdx:Int = -1
private var callCount:Int = 0
private val deck:PokerDeck = new PokerDeck()
def visibleHands:Map[String, PokerHand] = {
return hands.map((t) => (t._1, if (t._2 != null) new PokerHand(t._2.cards(0), t._2.cards(1)) else null)).toMap
}
def visibleState:PublicPokerState = {
return new PublicPokerState(
Map[String, PokerHand]() ++ visibleHands,
Map[String, Int]() ++ cash,
anteAmount,
betAmount,
totalPot,
currentPlayer,
playerIDFromIdx(nextPlayer.get))
}
def nextPlayer:Option[Int] = {
if (currentPlayerIdx != None) {
var result = currentPlayerIdx.get
do {
result = (result + 1) % players.length
} while (hands(playerIDFromIdx(result)) == null)
return Some(result)
} else {
return None
}
}
def currentPlayer:String = {
return playerIDFromIdx(currentPlayerIdx.get)
}
def activePlayerCount:Int = {
return hands.count((t) => t._2 != null)
}
// FIXME: if dealer has no money, program will fail
def deal(playerID:String):String = {
checkID(playerID)
currentPlayerIdx = Some(players.indexWhere((p) => p.id == playerID))
dealerIdx = currentPlayerIdx.get
deck.shuffle()
// reset pot & bet
betAmount = 0
totalPot = 0
println("New Game, " + names(playerID) + " dealing:")
println("")
// deal out cards
for (cardNum <- 0 until 5) {
for (playerNum <- 0 until players.length) {
val hand = hands(playerIDFromIdx(playerNum))
hand.cards(cardNum) = deck.nextCard()
}
}
// ante up
for (player <- players) {
if (cash(player.id) < anteAmount) {
hands(player.id) = null
println(player.name + " cannot ante.")
} else {
totalPot += anteAmount
cash(player.id) -= anteAmount
}
}
printAllHands(true)
println("")
return raise(playerID, 10)
}
def raise(playerID:String, amount:Int):String = {
checkCurrentPlayerID(playerID)
// update pot & bet
totalPot += betAmount + amount
betAmount = amount
// reset call count
callCount = 0
cash(playerID) -= betAmount + amount
println(names(playerID) + " sees the bet and raises $" + amount +
". (pot = $" + totalPot + ", bet = $" + betAmount + ")")
println("")
return endTurn()
}
def call(playerID:String):String = {
checkCurrentPlayerID(playerID)
totalPot += betAmount
cash(playerID) -= betAmount
callCount += 1
println(names(playerID) + " calls bet. (pot = $" + totalPot + ", bet = $" + betAmount + ")")
println("")
if (checkGameEnd()) {
return null
} else {
return endTurn()
}
}
def fold(playerID:String):String = {
checkCurrentPlayerID(playerID)
println(names(playerID) + " has folded.")
println("")
hands(playerID) = null
if (checkGameEnd()) {
return null
} else {
return endTurn()
}
}
def checkGameEnd():Boolean = {
activePlayerCount match {
case 0 =>
throw new IllegalStateException("No one's playing? SANITY CHECK FAILED")
case 1 => // everyone else folded
val winnerID = hands.find((t) => t._2 != null).get._1
cash(winnerID) += totalPot
totalPot = 0
printWinner(winnerID)
return true
case _ if activePlayerCount == callCount + 1 => // everyone else called
val nullHandValue = Tuple(-1, -1, -1, -1, -1, -1)
val (winnerID, winnerValue) = hands.map(
(t) => (t._1, if (t._2 != null) t._2.value else nullHandValue)).toList.sortBy(_._2).last
cash(winnerID) += totalPot
totalPot = 0
printWinner(winnerID)
return true
case _ => // game still going
return false
}
}
private def printAllHands(onlyVisible:Boolean) = {
val theHands = if (onlyVisible) visibleHands else Map[String, PokerHand]() ++ hands
val maxLength = players.map(_.name).maxBy(_.length).length
for ((playerID, hand) <- theHands) {
if (hand != null) {
val playerName = names(playerID)
var spaces = ""
Iterator.range(0, maxLength - playerName.length).foreach((n) => spaces += " ")
println(playerName + ": " + spaces + "[" + hand.toString() + "]")
}
}
}
private def printWinner(winnerID:String) = {
// print winner
println("WINNER: " + names(winnerID))
println("")
val maxLength = players.map(_.name).maxBy(_.length).length
// print hands
println("HANDS:")
for ((id, hand) <- hands) {
val playerName = names(id)
var spaces = ""
Iterator.range(0, maxLength - playerName.length).foreach((n) => spaces += " ")
if (hand != null) {
println(" " + playerName + ": " + spaces + hand.toString())
} else {
println(" " + playerName + ": " + spaces + "FOLDED")
}
}
println("")
// print cash
println("CASH:")
for ((id, amt) <- cash) {
val playerName = names(id)
var spaces = ""
Iterator.range(0, maxLength - playerName.length).foreach((n) => spaces += " ")
println(" " + names(id) + ": " + spaces + "$" + amt)
}
}
private def checkID(id:String) = {
if (!names.contains(id)) {
throw new IllegalArgumentException("'" + id + "' is not a valid player ID.")
}
}
def playerIDFromIdx(idx:Int):String = {
return players(idx).id
}
private def checkCurrentPlayerID(id:String) = {
if (currentPlayerIdx == null) {
throw new IllegalStateException("There is no game in progress!")
}
if (id != currentPlayer) {
throw new IllegalArgumentException("'" + id + "' is not the current player.")
}
}
private def endTurn():String = {
var idx = currentPlayerIdx.get
do {
idx = (idx + 1) % players.length
} while (hands(playerIDFromIdx(idx)) == null)
currentPlayerIdx = Some(idx)
return currentPlayer
}
}
/** Poker messages */
trait PokerMessage {}
case class Raise(playerID:String, amount:Int) extends PokerMessage
case class Deal(dealerID:String) extends PokerMessage
case class Call(playerID:String) extends PokerMessage
case class Fold(playerID:String) extends PokerMessage
case class MakeMove(state:PublicPokerState) extends PokerMessage
object PlayerQuestion extends Enumeration {
val AreYouBluffing, HavingFun = Value
}
object PlayerAnswer extends Enumeration {
val Yes, No, NoAnswer = Value
}
case class Ask(sourcePlayerID:String, message:PlayerQuestion.Value) extends PokerMessage
case class Answer(sourcePlayerID:String, message:PlayerAnswer.Value) extends PokerMessage
/** Poker players */
abstract class PokerPlayer(router:Router) extends Function2[Array[String], Any, Unit] with Player {
def apply(path:Array[String], message:Any) = {
val messages = act(message)
for ((recipient, message) <- messages) {
sendMessage(recipient, message)
}
}
def sendMessage(recipient:String, message:PokerMessage) = {
router.route("/player/" + recipient, message)
}
def name:String
def id:String
def act(message:Any):Map[String, PokerMessage]
}
class PokerSystem(router:Router, players:List[PokerPlayer]) extends PokerPlayer(router) {
private val game = new PokerState(players)
def act(message:Any):Map[String, PokerMessage] = {
message match {
case Deal(dealerID) =>
val firstPlayer = game.deal(dealerID)
return Map(firstPlayer -> new MakeMove(game.visibleState))
case Raise(playerID, amount) =>
val nextPlayer = game.raise(playerID, amount)
return Map(nextPlayer -> new MakeMove(game.visibleState))
case Call(playerID) =>
val nextPlayer = game.call(playerID)
if (nextPlayer != null) {
return Map(nextPlayer -> new MakeMove(game.visibleState))
} else {
return Map()
}
case Fold(playerID) =>
val nextPlayer = game.fold(playerID)
if (nextPlayer != null) {
return Map(nextPlayer -> new MakeMove(game.visibleState))
} else {
return Map()
}
}
}
def name:String = "System"
def id:String = "system"
}
abstract class LivingPlayer(router:Router) extends PokerPlayer(router) {
protected var lastKnownState:PublicPokerState = null
protected val random:Random = new Random()
def act(message:Any):Map[String, PokerMessage] = {
message match {
case MakeMove(state) =>
lastKnownState = state
return makeMoveImpl()
case Ask(sourcePlayerID, question) =>
return Map(sourcePlayerID -> new Answer(this.id, answerQuestion(sourcePlayerID, question)))
case Answer(sourcePlayerID, answer) =>
handleAnswer(sourcePlayerID, answer)
return makeMoveImpl()
}
}
private def makeMoveImpl():Map[String, PokerMessage] = {
val move = makeMove()
if (move != null) {
return Map("system" -> move)
} else {
return Map()
}
}
def answerQuestion(sourcePlayerID:String, question:PlayerQuestion.Value):PlayerAnswer.Value
def handleAnswer(sourcePlayerID:String, answer:PlayerAnswer.Value):Unit
def makeMove():PokerMessage
def speak(text:String) = {
println(this.name + ": " + text)
}
}
class Worf(router:Router) extends LivingPlayer(router) {
def answerQuestion(sourcePlayerID:String, question:PlayerQuestion.Value):PlayerAnswer.Value = {
speak("NOOO!")
return PlayerAnswer.No
}
def handleAnswer(sourcePlayerID:String, answer:PlayerAnswer.Value):Unit = {
// empty
}
def makeMove():PokerMessage = {
val r = random.nextInt(10)
if (r < 2) {
speak("TELL ME IF YOU'RE BLUFFING!")
sendMessage(lastKnownState.nextPlayer, new Ask(this.id, PlayerQuestion.AreYouBluffing))
return null
} else if (r < 4 && lastKnownState.canRaise(this.id, 10)) {
return new Raise(this.id, 10)
} else if (r < 8 && lastKnownState.canCall(this.id)) {
return new Call(this.id)
} else {
speak("Today is not a good day to die.")
return new Fold(this.id)
}
}
def name:String = "Worf"
def id:String = "worf"
}
class Data(router:Router) extends LivingPlayer(router) {
def answerQuestion(sourcePlayerID:String, question:PlayerQuestion.Value):PlayerAnswer.Value = {
speak("I do not wish to answer that.")
return PlayerAnswer.NoAnswer
}
def handleAnswer(sourcePlayerID:String, answer:PlayerAnswer.Value):Unit = {
// empty
}
def makeMove():PokerMessage = {
val r = random.nextInt(10)
if (r < 2) {
speak("Is that what is known as a bluff?")
sendMessage(lastKnownState.nextPlayer, new Ask(this.id, PlayerQuestion.AreYouBluffing))
return null
} else if (r < 6 && lastKnownState.canRaise(this.id, 10)) {
return new Raise(this.id, 10)
} else if (r < 9 && lastKnownState.canCall(this.id)) {
return new Call(this.id)
} else {
speak("Too rich for my biochemical lubricants.")
return new Fold(this.id)
}
}
def name:String = "Data"
def id:String = "data"
}
class Riker(router:Router) extends LivingPlayer(router) {
def answerQuestion(sourcePlayerID:String, question:PlayerQuestion.Value):PlayerAnswer.Value = {
speak("...")
return PlayerAnswer.NoAnswer
}
def handleAnswer(sourcePlayerID:String, answer:PlayerAnswer.Value):Unit = {
// empty
}
def makeMove():PokerMessage = {
val r = random.nextInt(10)
if (r < 2) {
speak("Enjoying yourself so far?")
sendMessage(lastKnownState.nextPlayer, new Ask(this.id, PlayerQuestion.HavingFun))
return null
} else if (r < 7 && lastKnownState.canRaise(this.id, 10)) {
return new Raise(this.id, 10)
} else if (r < 9 && lastKnownState.canCall(this.id)) {
return new Call(this.id)
} else {
speak("...")
return new Fold(this.id)
}
}
def name:String = "Commander Riker"
def id:String = "riker"
}
class DeannaTroi(router:Router) extends LivingPlayer(router) {
def answerQuestion(sourcePlayerID:String, question:PlayerQuestion.Value):PlayerAnswer.Value = {
if (random.nextInt(10) < 5) {
speak("Yes.")
return PlayerAnswer.Yes
} else {
speak("No.")
return PlayerAnswer.No
}
}
def handleAnswer(sourcePlayerID:String, answer:PlayerAnswer.Value):Unit = {
// empty
}
def makeMove():PokerMessage = {
val r = random.nextInt(10)
if (r < 2) {
speak("Not a very good hand you've got.")
sendMessage(lastKnownState.nextPlayer, new Ask(this.id, PlayerQuestion.AreYouBluffing))
return null
} else if (r < 7 && lastKnownState.canRaise(this.id, 10)) {
return new Raise(this.id, 10)
} else if (r < 9 && lastKnownState.canCall(this.id)) {
return new Call(this.id)
} else {
speak("(sigh) I fold.")
return new Fold(this.id)
}
}
def name:String = "Deanna"
def id:String = "deannatroi"
}
/** Testing singleton. */
object Test extends App {
val router = new Router(3)
val worf = new Worf(router)
val data = new Data(router)
val riker = new Riker(router)
val deanna = new DeannaTroi(router)
val players = (new Random()).shuffle(List(worf, data, riker, deanna))
router.addRoute("/player/worf", worf)
.addRoute("/player/data", data)
.addRoute("/player/riker", riker)
.addRoute("/player/deannatroi", deanna)
.addRoute("/player/system", new PokerSystem(router, players))
.setOnError((path: Array[String]) => println("Route failed: /" + path.reduceLeft(_ + "/" + _)))
router.initialize()
router.route("/player/system", new Deal(riker.id))
}
Test.main(args)
New Game, Commander Riker dealing:
Worf: [10C, 7C, --, --, --]
Commander Riker: [KD, 9H, --, --, --]
Deanna: [JD, JH, --, --, --]
Data: [AD, KC, --, --, --]
Commander Riker sees the bet and raises $10. (pot = $50, bet = $10)
Worf sees the bet and raises $10. (pot = $70, bet = $10)
Deanna sees the bet and raises $10. (pot = $90, bet = $10)
Data calls bet. (pot = $100, bet = $10)
Commander Riker sees the bet and raises $10. (pot = $120, bet = $10)
Worf: Today is not a good day to die.
Worf has folded.
Deanna calls bet. (pot = $130, bet = $10)
Data calls bet. (pot = $140, bet = $10)
WINNER: Data
HANDS:
Worf: FOLDED
Commander Riker: KD, 9H, 2H, 7D, 6S
Deanna: JD, JH, KH, QH, 7S
Data: AD, KC, 6H, 9D, AH
CASH:
Worf: $170
Commander Riker: $150
Deanna: $160
Data: $310
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment