Browse Source

Add rudimentary taboo logic

Thomas Dy 11 years ago
parent
commit
9afd0e4979
3 changed files with 334 additions and 1 deletions
  1. 5 0
      app/models/ChatRoom.scala
  2. 236 0
      app/models/Taboo.scala
  3. 93 1
      test/ApplicationSpec.scala

+ 5 - 0
app/models/ChatRoom.scala

@@ -56,6 +56,8 @@ object ChatRoom {
 
 class ChatRoom extends Actor {
 
+  val tabooGame = Akka.system.actorOf(Props(classOf[TabooGame], self))
+
   var members = Map.empty[String, Concurrent.Channel[JsValue]]
   val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
 
@@ -69,6 +71,7 @@ class ChatRoom extends Actor {
         members = members + (username -> personalChannel)
         sender ! Connected(chatEnumerator.interleave(personalEnumerator))
         self ! NotifyJoin(username)
+        tabooGame ! Join(username)
       }
     }
 
@@ -78,6 +81,7 @@ class ChatRoom extends Actor {
 
     case Talk(username, text) => {
       notifyAll("talk", username, text)
+      tabooGame ! Talk(username, text)
     }
 
     case Tell(username, text, to) => {
@@ -87,6 +91,7 @@ class ChatRoom extends Actor {
     case Quit(username) => {
       members = members - username
       notifyAll("quit", username, "has left the room")
+      tabooGame ! Quit(username)
     }
 
   }

+ 236 - 0
app/models/Taboo.scala

@@ -0,0 +1,236 @@
+package models
+
+import akka.actor._
+import scala.concurrent.duration._
+import play.api.libs.concurrent._
+import play.api.Play.current
+import play.api.libs.concurrent.Execution.Implicits._
+
+case class Card(word: String, taboo: Set[String]) {
+
+  def isTaboo(text: String) = {
+    val lower = text.toLowerCase
+    def contains(word: String) = {
+      lower.indexOf(word.toLowerCase) >= 0
+    }
+
+    // check if text contains word or anything in taboo
+    (taboo + word).map(contains).foldLeft(false)(_ || _)
+  }
+
+  def isCorrect(text: String) = {
+    text.toLowerCase.indexOf(word.toLowerCase) >= 0
+  }
+
+}
+
+class Team {
+  var members = List.empty[String]
+  var player = ""
+  var guessers = Set.empty[String]
+
+  def hasPlayer(user: String) = members.indexOf(user) >= 0
+
+  def nextPlayer() = {
+    val index = (members.indexOf(player) + 1) % members.size
+    player = members(index)
+    guessers = members.filterNot(_ == player).toSet
+    player
+  }
+
+  def join(user: String) = {
+    if(!hasPlayer(user)) {
+      members = members :+ user
+    }
+  }
+
+  def leave(user: String) = {
+    members = members.filterNot(_ == user)
+  }
+
+  def isEmpty = members.isEmpty
+
+  def size = members.size
+}
+
+case class Round(team: Team, monitors: Set[String])
+
+case class Information(text: String)
+case class Guess(username: String, text: String)
+case object Pass
+case class Taboo(username: String)
+
+case class Correct(username: String, card: Card)
+case class Invalid(card: Card)
+case class Passed(card: Card)
+case class Tabooed(username: String, card: Card)
+
+case object NextCard
+case object PrepRound
+case object StartRound
+case object End
+case class Points(points: Int)
+
+class TabooGame(val chatActor: ActorRef) extends Actor {
+  var ready = false
+  var round: Option[Round] = None
+  var roundActor: ActorRef = null
+
+  val teamA: Team = new Team
+  val teamB: Team = new Team
+
+  var currentTeam: Team = teamB
+  var opposingTeam: Team = teamA
+
+  def receive = {
+    case Join(username) =>
+      if(teamA.size < 2 || teamA.size <= teamB.size) {
+        teamA.join(username)
+      }
+      else {
+        teamB.join(username)
+      }
+      if(round.isEmpty) {
+        self ! PrepRound
+      }
+
+    case Quit(username) =>
+      teamA.leave(username)
+      teamB.leave(username)
+      if(!round.isEmpty && (teamA.size < 2 || (currentTeam == teamB && teamB.size < 2))) {
+        roundActor ! End
+      }
+
+    case Talk(username, text) => round match {
+      case Some(round) =>
+        if(username == round.team.player) {
+          if(text == "/pass") {
+            roundActor ! Pass
+          }
+          else {
+            roundActor ! Information(text)
+          }
+        }
+        else if(round.team.guessers(username)) {
+          roundActor ! Guess(username, text)
+        }
+        else if(round.monitors(username) && text == "/taboo") {
+          roundActor ! Taboo(username)
+        }
+
+      case None =>
+        if(username == currentTeam.player && text == "/start") {
+          self ! StartRound
+        }
+    }
+
+    case Correct(username, card) =>
+      nextCard(username+" got it right!", card)
+
+    case Invalid(card) =>
+      nextCard("Uh-uh! "+player+" said a taboo word. Sorry.", card)
+
+    case Passed(card) =>
+      nextCard(player+" has passed.", card)
+
+    case Tabooed(username, card) =>
+      nextCard("Oh no! "+player+" apparently said a taboo word. :(", card)
+
+    case Points(points) =>
+      chatActor ! Talk("*GM", "The round is over. The team got "+points)
+      round = None
+      ready = false
+      self ! PrepRound
+
+    case NextCard =>
+      val card = randomCard()
+      roundActor ! card
+
+      (round.get.monitors + player).foreach { user =>
+        chatActor ! Tell(
+          "*GM",
+          "The word is "+card.word+". The taboo words are: "+card.taboo.reduceLeft(_+" "+_),
+          user)
+      }
+
+    case PrepRound =>
+      if(!ready && teamA.size >= 2) {
+        ready = true
+
+        if(teamB.size < 2) {
+          currentTeam = teamA
+          opposingTeam = teamB
+        }
+        else {
+          val temp = currentTeam
+          currentTeam = opposingTeam
+          opposingTeam = temp
+        }
+        currentTeam.nextPlayer()
+
+        chatActor ! Talk("*GM", "Next round, the player will be "+currentTeam.player)
+        chatActor ! Tell("*GM", "Type /start to start the round", currentTeam.player)
+      }
+
+    case StartRound =>
+      round = Some(Round(currentTeam, opposingTeam.members.toSet))
+      roundActor = context.actorOf(Props[TabooRound])
+      Akka.system.scheduler.scheduleOnce(1 minute, roundActor, End)
+      self ! NextCard
+  }
+
+  def player = round.get.team.player
+
+  def randomCard() = Card("test", Set("a", "b", "c", "d", "e"))
+
+  def nextCard(message: String, oldCard: Card) = {
+    chatActor ! Talk("*GM", message+" The word was "+oldCard.word+".")
+    self ! NextCard
+  }
+
+  self ! PrepRound
+}
+
+class TabooRound extends Actor {
+  var card: Option[Card] = None
+  var points = 0
+
+  def receive = {
+    case newCard: Card =>
+      card = Some(newCard)
+
+    case Guess(username, text) => card.map { card =>
+      if(card.isCorrect(text)) {
+        points += 1
+        sender ! Correct(username, card)
+        this.card = None
+      }
+    }
+
+    case Information(text) => card.map { card =>
+      if(card.isTaboo(text)) {
+        points -= 1
+        sender ! Invalid(card)
+        this.card = None
+      }
+    }
+
+    case Pass => card.map { card =>
+      points -= 1
+      sender ! Passed(card)
+      this.card = None
+    }
+
+    case Taboo(username) => card.map { card =>
+      points -= 1
+      sender ! Tabooed(username, card)
+      this.card = None
+    }
+
+    case End =>
+      sender ! Points(points)
+      context.stop(self)
+
+  }
+
+}

+ 93 - 1
test/ApplicationSpec.scala

@@ -5,10 +5,102 @@ import org.junit.runner._
 import play.api.test._
 import play.api.test.Helpers._
 
+import models.Card
+import models.Team
+
 @RunWith(classOf[JUnitRunner])
 class ApplicationSpec extends Specification {
 
-  "Application" should {
+  "Card" should {
+
+    val card = Card("test", Set("these", "words", "are", "not", "allowed"))
+
+    "Check Taboo words correctly" in {
+      card.isTaboo("test") must beTrue
+      card.isTaboo("these") must beTrue
+      card.isTaboo("words") must beTrue
+      card.isTaboo("are") must beTrue
+      card.isTaboo("not") must beTrue
+      card.isTaboo("allowed") must beTrue
+    }
+
+    "Check guessed words correctly" in {
+      card.isCorrect("test") must beTrue
+      card.isCorrect("these") must beFalse
+      card.isCorrect("words") must beFalse
+      card.isCorrect("are") must beFalse
+      card.isCorrect("not") must beFalse
+      card.isCorrect("allowed") must beFalse
+    }
+
+    "Check case insensitively" in {
+      card.isCorrect("test") must beTrue
+      card.isCorrect("Test") must beTrue
+      card.isCorrect("tEst") must beTrue
+      card.isCorrect("teSt") must beTrue
+      card.isCorrect("tesT") must beTrue
+      card.isCorrect("TEST") must beTrue
+      card.isTaboo("These") must beTrue
+      card.isTaboo("wOrds") must beTrue
+      card.isTaboo("arE") must beTrue
+      card.isTaboo("nOt") must beTrue
+      card.isTaboo("ALLOWED") must beTrue
+    }
+
+    "Check the entire message" in {
+      card.isTaboo("The word is test") must beTrue
+      card.isTaboo("I am not allowed to say") must beTrue
+      card.isTaboo("swords") must beTrue
+    }
+
   }
 
+  "Team" should {
+    val team = new Team()
+
+    "Allow players to join" in {
+      team.join("player1")
+      team.join("player2")
+      team.join("player2")
+      team.join("player3")
+
+      team.hasPlayer("player1") must beTrue
+      team.hasPlayer("player2") must beTrue
+      team.hasPlayer("player3") must beTrue
+      team.hasPlayer("player4") must beFalse
+    }
+
+    "Allow players to leave" in {
+      team.leave("player3")
+      team.hasPlayer("player3") must beFalse
+
+      // should be a noop
+      team.leave("player3")
+      team.hasPlayer("player3") must beFalse
+    }
+
+    "Cycle between players correctly" in {
+      team.nextPlayer() must equalTo("player1")
+      team.nextPlayer() must equalTo("player2")
+      team.nextPlayer() must equalTo("player1")
+      team.join("player3")
+      team.join("player4")
+      team.nextPlayer() must equalTo("player2")
+      team.nextPlayer() must equalTo("player3")
+      team.nextPlayer() must equalTo("player4")
+      team.nextPlayer() must equalTo("player1")
+      team.nextPlayer() must equalTo("player2")
+      team.leave("player3")
+      team.nextPlayer() must equalTo("player4")
+      team.leave("player4")
+      team.nextPlayer() must equalTo("player1")
+      team.nextPlayer() must equalTo("player2")
+    }
+
+    "Give the correct current player and guessers" in {
+      team.nextPlayer() must equalTo("player1")
+      team.player must equalTo("player1")
+      team.guessers("player2") must beTrue
+    }
+  }
 }