Created
April 26, 2010 06:08
-
-
Save mmakowski/379031 to your computer and use it in GitHub Desktop.
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
// concepts: | |
// - actors | |
// - pattern matching | |
// - Option (None/Some(x)) | |
// -- enums etc | |
object Figure extends Enumeration { | |
type Figure = Value | |
val Paper, Scissors, Stone = Value | |
} | |
import Figure._ | |
object Messages { | |
case object Play | |
case object End | |
case class StartMatch(toWins: Int) | |
case class Move(figure: Figure) | |
} | |
import Messages._ | |
// -- the app | |
object PaperScissorsStoneApp { | |
def main(args: Array[String]) { | |
val player1 = new Player(1) | |
val player2 = new Player(2) | |
val referee = new Referee(player1, player2) | |
player1.start() | |
player2.start() | |
referee.start() | |
referee ! StartMatch(args(0) toInt) | |
} | |
} | |
import scala.actors._ | |
// -- the player | |
class Player(val id: Int) extends Actor { | |
val random = new scala.util.Random() | |
def act() { | |
loop { | |
receive { | |
case Play => sender ! Move(chooseFigure()) | |
case End => exit() | |
} | |
} | |
} | |
def chooseFigure() = (random.nextInt(3)) match { | |
case 0 => Paper | |
case 1 => Scissors | |
case 2 => Stone | |
} | |
} | |
// -- the referee | |
class Referee(val player1: Player, val player2: Player) extends Actor { | |
var toWins = 0 | |
import scala.collection.mutable.Map | |
val wins = Map(player1 -> 0, player2 -> 0) | |
val currentMove = Map[Player, Option[Figure]](player1 -> None, player2 -> None) | |
def act() { | |
loop { | |
receive { | |
case StartMatch(toWins) => startMatch(toWins) | |
case Move(figure) => recordMove(sender.asInstanceOf[Player], figure) | |
} | |
} | |
} | |
def startMatch(toWins: Int) { | |
this.toWins = toWins | |
startRound() | |
} | |
def startRound() { | |
println("round starting") | |
currentMove(player1) = None | |
currentMove(player2) = None | |
player1 ! Play | |
player2 ! Play | |
} | |
def recordMove(player: Player, figure: Figure) { | |
println("player " + player.id + " played " + figure) | |
currentMove(player) = Some(figure) | |
if (!currentMove.values.exists(_ == None)) finishRound() | |
} | |
def finishRound() = winnerOfCurrentRound() match { | |
case None => recordDraw() | |
case Some(player) => recordWin(player) | |
} | |
def winnerOfCurrentRound() = (currentMove(player1).get, currentMove(player2).get) match { | |
case (Paper, Paper) => None | |
case (Paper, Scissors) => Some(player2) | |
case (Paper, Stone) => Some(player1) | |
case (Scissors, Paper) => Some(player1) | |
case (Scissors, Scissors) => None | |
case (Scissors, Stone) => Some(player2) | |
case (Stone, Paper) => Some(player2) | |
case (Stone, Scissors) => Some(player1) | |
case (Stone, Stone) => None | |
} | |
def recordDraw() { | |
println("round ended in a draw") | |
startRound() | |
} | |
def recordWin(player: Player) { | |
wins(player) += 1 | |
println("player " + player.id + " won this round; overall score: " + wins(player1) + ":" + wins(player2)) | |
if (wins(player) == toWins) { | |
println("player " + player.id + " won the match") | |
finishGame() | |
} else startRound() | |
} | |
def finishGame() { | |
player1 ! End | |
player2 ! End | |
exit() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The Problem
We'd like to implement a game of paper/scissors/stone (or the same things in a different order) where two players would play a match until one of the players win the specified number of games.
We implement it by creating active objects -- actors -- representing the two players and an actor representing a referee.
Demo
[run the app]
Actors
An actor implements
act()
method in which it receives messages.Play
(to which they reply with a randomly chosen figure) andEnd
messages (which causes them to terminate)StartMatch
(which starts a new match) andMove
(which updates the state of the current round)Actors have to be started before they can respond to any messages; this is done in
main()
. The syntax for message send isactor ! message
.Case classes
StartMatch
andMove
are defined as case classes. It's a syntactic sugar which creates a class that can be used like a type constructor from functional languages. The "class parameters" are in fact the parameters of the main constructor; they are persisted as fields of the class and appropriateequals
,hashCode
andtoString
methods are automatically generated.Option
In Java every method that returns a reference can potentially return a null. In Scala we can make the users of the method aware that the method/function can potentially give no results by wrapping the returned type
T
inOption
:Option[T]
. The function can then returnNone
to indicate lack of value orSome(value)
otherwise.Pattern matching
finishRound()
works out whether someone has won by detecting if theOption
returned is aNone
orSome(player)
. Note that when doing so it is able to unwrap the player reference fromSome
.