Created
April 2, 2012 01:41
-
-
Save diosmosis/2279948 to your computer and use it in GitHub Desktop.
Scala Experiment Iteration 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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