Skip to content

Instantly share code, notes, and snippets.

@diosmosis
Created April 2, 2012 01:41
Show Gist options
  • Save diosmosis/2279948 to your computer and use it in GitHub Desktop.
Save diosmosis/2279948 to your computer and use it in GitHub Desktop.
Scala Experiment Iteration 3
import scala.util.Random
import com.scalaiteration.router.Router
import com.scalaiteration.poker.game._
import com.scalaiteration.poker.players._
/** Testing singleton. */
object Test extends App {
val router = new Router(3)
val system = new PokerSystem(router)
val worf = new Worf(router, system)
val data = new Data(router, system)
val riker = new Riker(router, system)
val deanna = new DeannaTroi(router, system)
val players = (new Random()).shuffle(List(worf, data, riker, deanna))
system.setPlayers(players)
router.addRoute("/player/worf", worf)
.addRoute("/player/data", data)
.addRoute("/player/riker", riker)
.addRoute("/player/deannatroi", deanna)
.addRoute("/player/system", system)
.setOnError((path: Array[String]) => println("Route failed: /" + path.reduceLeft(_ + "/" + _)))
router.initialize()
router.route("/player/system", new Deal(riker.id))
}
import com.scalaiteration.router.Router
import com.scalaiteration.poker.game.{PlayerAnswer, PlayerQuestion, LivingPlayer, PokerSystem}
import com.scalaiteration.poker.rules.{Answer, Player, Random, Ask, Raise, Call, Fold}
package com.scalaiteration.poker.players {
class Worf(router:Router, system:PokerSystem) extends LivingPlayer(router, system) {
def name:String = "Worf"
def id:String = "worf"
// rules
rules ::= Answer(PlayerAnswer.No, "NOOO!") when Player().question != null
rules ::= Ask("+1", PlayerQuestion.AreYouBluffing, "TELL ME IF YOU'RE BLUFFING!") when Random(10) < 2
rules ::= Raise(10) when Random(10) < 4
rules ::= Call() when Random(10) < 8
rules ::= Fold("Today is not a good day to die.")
}
class Data(router:Router, system:PokerSystem) extends LivingPlayer(router, system) {
def name:String = "Data"
def id:String = "data"
// rules
rules ::= Answer(PlayerAnswer.NoAnswer, "I do not wish to answer that.") when Player().question != null
rules ::= Ask("+1", PlayerQuestion.AreYouBluffing, "Is that what is known as a bluff?") when Random(10) < 2
rules ::= Raise(10) when Random(10) < 6
rules ::= Call() when Random(10) < 9
rules ::= Fold("Too rich for my biochemical lubricants.")
}
class Riker(router:Router, system:PokerSystem) extends LivingPlayer(router, system) {
def name:String = "Commander Riker"
def id:String = "riker"
// rules
rules ::= Answer(PlayerAnswer.NoAnswer, "...") when Player().question != null
rules ::= Ask("+1", PlayerQuestion.HavingFun, "Enjoying yourself so far?") when Random(10) < 2
rules ::= Raise(10) when Random(10) < 7
rules ::= Call() when Random(10) < 9
rules ::= Fold("...")
}
class DeannaTroi(router:Router, system:PokerSystem) extends LivingPlayer(router, system) {
def name:String = "Deanna"
def id:String = "deannatroi"
// rules
rules ::= Answer(PlayerAnswer.Yes, "Yes.") when Player().question != null
rules ::= Ask("+1", PlayerQuestion.AreYouBluffing, "Not a very good hand you've got.") when Random(10) < 2
rules ::= Raise(10) when Random(10) < 5
rules ::= Call() when Random(10) < 9
rules ::= Fold("(sigh) I fold.")
}
}
import scala.util.Random
import scala.math.Ordering._
import scala.util.Sorting
import scala.collection.mutable.{HashMap => MutableHashMap}
import com.scalaiteration.router.Router
import com.scalaiteration.poker.rules.RuleSet
package com.scalaiteration.poker.game {
trait Player {
def name:String
def id:String
def getGivenAnswer(fromID:String):String
def getAskedQuestion(fromID:String):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 currentPlayerID:String,
val nextPlayer:String,
val players:List[Player]) {
def currentPlayer:Player = {
return getPlayerById(currentPlayerID)
}
def canRaise(playerID:String, amount:Int):Boolean = {
return cash(playerID) >= betAmount + amount
}
def canCall(playerID:String):Boolean = {
return cash(playerID) >= betAmount
}
def getPlayer(specifier:String):String = {
if (specifier.startsWith("+") || specifier.startsWith("-")) {
var intermediate = specifier
if (specifier.startsWith("+")) {
intermediate = specifier.substring(1)
}
val player = intermediate.toInt % players.length
return players(player).id
} else {
return specifier
}
}
def getPlayerById(id:String):Player = {
for (p <- players) {
if (p.id == id) {
return p
}
}
return null
}
}
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),
players)
}
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 = ((-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, messageType:PlayerQuestion.Value, message:String) extends PokerMessage
case class Answer(sourcePlayerID:String, messageType:PlayerAnswer.Value, message:String) 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 (recipient, outMessage) = act(message.asInstanceOf[PokerMessage])
sendMessage(recipient, outMessage)
}
def sendMessage(recipient:String, message:PokerMessage) = {
if (recipient != null) {
router.route("/player/" + recipient, message)
}
}
def name:String
def id:String
def act(message:PokerMessage):(String, PokerMessage)
}
class PokerSystem(router:Router) extends PokerPlayer(router) {
private var game:PokerState = null
private var players:List[PokerPlayer] = null
def setPlayers(players:List[PokerPlayer]):Unit = {
this.players = players
this.game = new PokerState(players)
}
def visibleState:PublicPokerState = {
return game.visibleState
}
def act(message:PokerMessage):(String, PokerMessage) = {
message match {
case Deal(dealerID) =>
val firstPlayer = game.deal(dealerID)
return ((firstPlayer, new MakeMove(game.visibleState)))
case Raise(playerID, amount) =>
val nextPlayer = game.raise(playerID, amount)
return ((nextPlayer, new MakeMove(game.visibleState)))
case Call(playerID) =>
val nextPlayer = game.call(playerID)
if (nextPlayer != null) {
return ((nextPlayer, new MakeMove(game.visibleState)))
} else {
return ((null, null))
}
case Fold(playerID) =>
val nextPlayer = game.fold(playerID)
if (nextPlayer != null) {
return ((nextPlayer, new MakeMove(game.visibleState)))
} else {
return ((null, null))
}
}
}
def name:String = "System"
def id:String = "system"
def getGivenAnswer(fromID:String):String = {
return null
}
def getAskedQuestion(fromID:String):String = {
return null
}
}
abstract class LivingPlayer(router:Router, system:PokerSystem) extends PokerPlayer(router) {
protected var lastKnownState:PublicPokerState = null
protected val currentAsked:MutableHashMap[String, String] = MutableHashMap[String, String]()
protected val currentAnswered:MutableHashMap[String, String] = MutableHashMap[String, String]()
protected val rules = new RuleSet()
def act(message:PokerMessage):(String, PokerMessage) = {
if (lastKnownState == null) {
lastKnownState = system.visibleState
}
message match {
case MakeMove(state) =>
return rules.processMessage(system, state, message)
case Ask(id, messageType, question) =>
val (to, outMessage) = rules.processMessage(lastKnownState.getPlayerById(id), lastKnownState, message)
outMessage match {
case Answer(id, messageType, answer) =>
return ((to, outMessage))
case _ =>
return ((id, new Answer(this.id, PlayerAnswer.NoAnswer, "...")))
}
case Answer(id, messageType, answer) =>
return rules.processMessage(lastKnownState.getPlayerById(id), lastKnownState, message)
}
}
def getGivenAnswer(fromID:String):String = {
return currentAnswered.getOrElse(fromID, null)
}
def getAskedQuestion(fromID:String):String = {
return currentAsked.getOrElse(fromID, null)
}
}
}
import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.immutable.HashMap
import scala.collection.mutable.{HashMap => MutableHashMap}
package com.scalaiteration.router {
/**
* 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
}
}
}
import scala.util.{Random => RandomGen}
import scala.math.Ordered
import scala.math.Ordering._
import scala.util.Sorting
import scala.runtime.RichInt
import scala.collection.immutable.StringOps
import scala.collection.mutable.MutableList
import com.scalaiteration.poker.game.{PokerMessage,
PublicPokerState,
PokerHand,
Player => GamePlayer,
PlayerQuestion => PlayerQuestionEnum,
PlayerAnswer => PlayerAnswerEnum,
Ask => AskMessage,
Call => CallMessage,
Raise => RaiseMessage,
Fold => FoldMessage,
Answer => AnswerMessage}
package com.scalaiteration.poker.rules {
class Condition[I](val lhs:RuleNoun[I], val rhs:I, val operator:Function2[Ordered[I], I, Boolean]) {
def execute(state:PublicPokerState, message:PokerMessage):Boolean = {
return operator(lhs.get(state, message), rhs)
}
}
// nouns
abstract class RuleNoun[I]() {
def ==(rhs:I):Condition[I] = {
return new Condition[I](this, rhs, (l, r) => l == r)
}
def !=(rhs:I):Condition[I] = {
return new Condition[I](this, rhs, (l, r) => l != r)
}
def <(rhs:I):Condition[I] = {
return new Condition[I](this, rhs, (l, r) => l < r)
}
def >(rhs:I):Condition[I] = {
return new Condition[I](this, rhs, (l, r) => l > r)
}
def <=(rhs:I):Condition[I] = {
return new Condition[I](this, rhs, (l, r) => l <= r)
}
def >=(rhs:I):Condition[I] = {
return new Condition[I](this, rhs, (l, r) => l >= r)
}
def get(state:PublicPokerState, message:PokerMessage):Ordered[I]
}
class OrderedPokerHand(val hand:PokerHand) extends Ordered[PokerHand] {
val value = hand.value
def ==(rhs:PokerHand):Boolean = {
return value == rhs.value
}
def !=(rhs:PokerHand):Boolean = {
return value != rhs.value
}
def compare(that:PokerHand):Int = {
return Ordering[(Int, Int, Int, Int, Int, Int)].compare(value, that.value)
}
}
abstract class PlayerSpecificNoun[I](var playerID:String) extends RuleNoun[I] {}
class AnyPlayerAdapter[V](
val state:PublicPokerState, val message:PokerMessage, val nounCopy:PlayerSpecificNoun[V]
) extends Ordered[V] {
private def op_impl(rhs:V, op:Function2[Ordered[V], V, Boolean]):Boolean = {
if (state == null) {
return false
}
for (player <- state.players) {
nounCopy.playerID = player.id
if (op(nounCopy.get(state, message), rhs)) {
return true
}
}
return false
}
override def equals(rhs:Any):Boolean = {
return this.op_impl(rhs.asInstanceOf[V], (l, r) => l == r)
}
def compare(that:V):Int = {
throw new IllegalStateException("not implemented")
}
override def <(rhs:V):Boolean = {
return this.op_impl(rhs, (l, r) => l < r)
}
override def >(rhs:V):Boolean = {
return this.op_impl(rhs, (l, r) => l > r)
}
override def <=(rhs:V):Boolean = {
return this.op_impl(rhs, (l, r) => l <= r)
}
override def >=(rhs:V):Boolean = {
return this.op_impl(rhs, (l, r) => l >= r)
}
}
case class Hand(val player:String = null) extends PlayerSpecificNoun[PokerHand](player) {
override def get(state:PublicPokerState, message:PokerMessage):Ordered[PokerHand] = {
if (this.playerID == null) {
return new AnyPlayerAdapter[PokerHand](state, message, new Hand())
} else {
return new OrderedPokerHand(state.visibleHands(this.playerID))
}
}
}
case class Cash(val player:String = null) extends PlayerSpecificNoun[Int](player) {
override def get(state:PublicPokerState, message:PokerMessage):Ordered[Int] = {
if (this.playerID == null) {
return new AnyPlayerAdapter[Int](state, message, new Cash())
} else {
return state.cash(this.playerID)
}
}
}
case class PlayerAnswer(val player:String = null) extends PlayerSpecificNoun[String](player) {
override def get(state:PublicPokerState, message:PokerMessage):Ordered[String] = {
if (this.playerID == null) {
return new AnyPlayerAdapter[String](state, message, new PlayerAnswer())
} else {
return state.currentPlayer.getGivenAnswer(this.playerID)
}
}
}
case class PlayerQuestion(val player:String = null) extends PlayerSpecificNoun[String](player) {
override def get(state:PublicPokerState, message:PokerMessage):Ordered[String] = {
if (this.playerID == null) {
return new AnyPlayerAdapter[String](state, message, new PlayerQuestion())
} else {
return state.currentPlayer.getAskedQuestion(this.playerID)
}
}
}
case class Player(val playerID:String = null) extends RuleNoun[String] {
def hand:Hand = {
return new Hand(playerID)
}
def cash:Cash = {
return new Cash(playerID)
}
def answer:PlayerAnswer = {
return new PlayerAnswer(playerID)
}
def question:PlayerQuestion = {
return new PlayerQuestion(playerID)
}
override def get(state:PublicPokerState, message:PokerMessage):Ordered[String] = {
throw new IllegalStateException("Player is an intermediate type.")
}
}
case class Pot() extends RuleNoun[Int] {
override def get(state:PublicPokerState, message:PokerMessage):Ordered[Int] = {
return state.totalPot
}
}
case class Random(max:Int = 10) extends RuleNoun[Int] {
private val random:RandomGen = new RandomGen()
override def get(state:PublicPokerState, message:PokerMessage):Ordered[Int] = {
return random.nextInt(max)
}
}
// verbs
abstract class RuleVerb {
var condition:Condition[_] = null
def when(condition:Condition[_]):RuleVerb = {
this.condition = condition
return this
}
def matches(state:PublicPokerState, message:PokerMessage):Boolean = {
if (condition == null) {
return true
} else {
return condition.execute(state, message)
}
}
def getMessageTarget(state:PublicPokerState, sourcePlayer:GamePlayer):String = {
return null
}
def getMessageToSend(state:PublicPokerState, sourcePlayer:GamePlayer):PokerMessage
}
case class Ask(val playerID:String, val questionType:PlayerQuestionEnum.Value, val question:String) extends RuleVerb {
override def getMessageTarget(state:PublicPokerState, sourcePlayer:GamePlayer):String = {
return playerID
}
def getMessageToSend(state:PublicPokerState, sourcePlayer:GamePlayer):PokerMessage = {
println(state.currentPlayer.name + ": " + question)
return new AskMessage(state.currentPlayer.id, questionType, question)
}
}
case class Answer(val answerType:PlayerAnswerEnum.Value, val answer:String) extends RuleVerb {
override def getMessageTarget(state:PublicPokerState, sourcePlayer:GamePlayer):String = {
return sourcePlayer.id
}
def getMessageToSend(state:PublicPokerState, sourcePlayer:GamePlayer):PokerMessage = {
println(state.currentPlayer.name + ": " + answer)
return new AnswerMessage(state.currentPlayer.id, answerType, answer)
}
override def matches(state:PublicPokerState, message:PokerMessage):Boolean = {
message match {
case AskMessage(id, type_, question) => return super.matches(state, message)
case _ => return false
}
}
}
case class Fold(val message:String = null) extends RuleVerb {
def getMessageToSend(state:PublicPokerState, sourcePlayer:GamePlayer):PokerMessage = {
if (message != null) {
println(state.currentPlayer.name + ": " + message)
}
return new FoldMessage(state.currentPlayer.id)
}
}
case class Raise(amount:Int) extends RuleVerb {
def getMessageToSend(state:PublicPokerState, sourcePlayer:GamePlayer):PokerMessage = {
return new RaiseMessage(state.currentPlayer.id, amount)
}
override def matches(state:PublicPokerState, message:PokerMessage):Boolean = {
return state.canRaise(state.currentPlayer.id, amount) && super.matches(state, message)
}
}
case class Call() extends RuleVerb {
def getMessageToSend(state:PublicPokerState, sourcePlayer:GamePlayer):PokerMessage = {
return new CallMessage(state.currentPlayer.id)
}
override def matches(state:PublicPokerState, message:PokerMessage):Boolean = {
return state.canCall(state.currentPlayer.id) && super.matches(state, message)
}
}
// rule set
class RuleSet {
val actions:MutableList[RuleVerb] = new MutableList[RuleVerb]()
def ::=(action:RuleVerb):Unit = {
if (action.condition == null) {
for (a <- this.actions) {
if (a.condition == null) {
throw new IllegalStateException("RuleSet already has a default rule.")
}
}
}
actions += action
}
def processMessage(sourcePlayer:GamePlayer, state:PublicPokerState, message:PokerMessage):(String, PokerMessage) = {
for (action <- this.actions) {
if (action.matches(state, message)) {
var tgt = action.getMessageTarget(state, sourcePlayer)
if (tgt == null) {
tgt = "system"
} else if (state != null) {
tgt = state.getPlayer(tgt)
}
return ((tgt, action.getMessageToSend(state, sourcePlayer)))
}
}
return null
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment