Created
January 1, 2012 04:02
-
-
Save diosmosis/1546201 to your computer and use it in GitHub Desktop.
Scala Experiment Iteration 2
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} | |
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) |
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
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