Skip to content

Instantly share code, notes, and snippets.

@tOverney
Last active November 6, 2017 12:52
Show Gist options
  • Save tOverney/47a67e0a7dc3eecfcc151b54c5f7e18f to your computer and use it in GitHub Desktop.
Save tOverney/47a67e0a7dc3eecfcc151b54c5f7e18f to your computer and use it in GitHub Desktop.
import akka.actor._
// These are the messages sent by the Scrutineer
case object VoteIsOver
case class IssueVoted(val question: String)
// These are all the messages sent by an Assembly member
case object ImHere
sealed trait VoteOption
case object Yes extends VoteOption
case object No extends VoteOption
case object Blank extends VoteOption
// Message related to the VoteTimer
case object TimesUp
case class StartTimer(duration: Long)
object Quorum extends App{
val system = ActorSystem("Quorum")
val AssemblySize: Int = 153
val VotationItem: IssueVoted = IssueVoted("raise the EPFL tuition fee")
val scrutineer =
system.actorOf(Props(new Scrutineer(AssemblySize, VotationItem)))
val AssemblyMembers = (0 to AssemblySize).map{
id => system.actorOf(Props(new AssemblyMember(scrutineer,
(10000 * Math.random()).toInt)), name = s"Assembly_member_$id")
}
}
class Scrutineer(assemblySize: Int, votationItem: IssueVoted) extends Actor {
val AbsoluteMajority = assemblySize/2 + 1
val VoteDuration = 50000L
val timer = context.actorOf(Props(new Timer))
override def receive = waitingToStart()
def waitingToStart(presentAssemblyMembers: Set[ActorRef] = Set()): Receive = {
case ImHere =>
val presentMembers = presentAssemblyMembers + context.sender()
val currentItem = 0
if (presentMembers.size > assemblySize/2) {
startVoteOn(presentMembers)
context.become(voting(alreadyVoted = Set(), presentMembers,
yesNoVotes = (0, 0)))
} else {
context.become(waitingToStart(presentMembers))
}
}
def voting(alreadyVoted: Set[ActorRef],
presentMembers: Set[ActorRef], yesNoVotes: (Int, Int)): Receive = {
case ImHere =>
val newMember = context.sender()
if (!presentMembers(newMember)) {
newMember ! votationItem
context.become(voting(alreadyVoted,
presentMembers + newMember, yesNoVotes))
}
case _: VoteOption if alreadyVoted(context.sender()) =>
println("You cannot vote twice on the same issue :" + context.sender())
case v: VoteOption =>
val (yesVotes, noVotes) = yesNoVotes
val votes = v match {
case Blank => yesNoVotes
case Yes => (yesVotes + 1, noVotes)
case No => (yesVotes, noVotes + 1)
}
val voteCasted = alreadyVoted + context.sender()
tallyVotes(votes, voteCasted, presentMembers,
presentMembers == voteCasted){ () =>
context.become(voting(voteCasted, presentMembers, votes))
}
case TimesUp =>
tallyVotes(yesNoVotes, alreadyVoted, presentMembers,
isOver = true)(() => {})
}
def done(): Receive = {
case _ => println(s"The voting session is over, it is too late to vote " +
context.sender())
}
def startVoteOn(members: Set[ActorRef]): Unit = {
println(s"Do you agree to ${votationItem.question}?")
members foreach(_ ! votationItem)
timer ! StartTimer(VoteDuration)
}
def tallyVotes(votes: (Int, Int), alreadyVoted: Set[ActorRef],
members: Set[ActorRef], isOver: Boolean)(cont: () => Unit): Unit = {
lazy val IssueVoted(question) = votationItem
val (yesVotes, noVotes) = votes
def endVote(message: String): Unit = {
(members -- alreadyVoted) foreach(_ ! VoteIsOver)
println(message)
context.become(done())
}
if (yesVotes >= AbsoluteMajority)
endVote(s"The Assembly agreed to $question by an absolute majority!")
else if (noVotes >= AbsoluteMajority)
endVote(s"The Assembly refused to $question by an absolute majority!")
else if (isOver) {
if (yesVotes > noVotes)
endVote(s"The Assembly agreed to $question: $yesVotes against $noVotes votes!")
else if (yesVotes == noVotes)
endVote(s"The Assembly was unable to choose whether to $question or not")
else
endVote(s"The Assembly refused to $question: $noVotes against $yesVotes votes!")
} else cont()
}
}
class AssemblyMember(scrutineer: ActorRef, thinkDelay: Int) extends Actor {
val timer = context.actorOf(Props(new Timer))
scrutineer ! ImHere
override def receive = waiting()
def waiting(): Receive = {
case issue: IssueVoted =>
timer ! StartTimer(thinkDelay)
context.become(thinking())
}
def thinking(): Receive = {
case VoteIsOver =>
context.become(waiting())
case TimesUp =>
Math.random() match {
case rng if rng < 0.45 => scrutineer ! Yes
case rng if rng < 0.9 => scrutineer ! No
case _ => scrutineer ! Blank
}
context.become(waiting())
}
}
class Timer extends Actor {
override def receive: Receive = {
case StartTimer(duration: Long) =>
Thread.sleep(duration)
context.sender() ! TimesUp
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment