Taboo.scala 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. package models
  2. import util._
  3. import akka.actor._
  4. import scala.concurrent.duration._
  5. import play.api.libs.concurrent._
  6. import play.api.libs.json._
  7. import play.api.Play.current
  8. import play.api.libs.concurrent.Execution.Implicits._
  9. object Team {
  10. implicit val writes = new Writes[Team] {
  11. def writes(o: Team) = Json.obj(
  12. "members" -> o.members,
  13. "player" -> o.player,
  14. "guessers" -> o.guessers
  15. )
  16. }
  17. }
  18. class Team {
  19. var members = List.empty[String]
  20. var player = ""
  21. var guessers = Set.empty[String]
  22. def hasPlayer(user: String) = members.indexOf(user) >= 0
  23. def nextPlayer() = {
  24. val index = (members.indexOf(player) + 1) % members.size
  25. player = members(index)
  26. guessers = members.filterNot(_ == player).toSet
  27. player
  28. }
  29. def join(user: String) = {
  30. if(!hasPlayer(user)) {
  31. members = members :+ user
  32. }
  33. }
  34. def leave(user: String) = {
  35. members = members.filterNot(_ == user)
  36. }
  37. def isEmpty = members.isEmpty
  38. def size = members.size
  39. }
  40. object Round {
  41. val DURATION = 60
  42. implicit val writes = new Writes[Round] {
  43. def writes(o: Round) = Json.obj(
  44. "team" -> o.team,
  45. "monitors" -> o.monitors,
  46. "remainingTime" -> o.remainingTime
  47. )
  48. }
  49. }
  50. case class Round(
  51. team: Team,
  52. monitors: Set[String],
  53. startTime: Long = System.currentTimeMillis
  54. ) {
  55. def remainingTime = {
  56. val sinceStart = (System.currentTimeMillis - startTime) / 1000
  57. Math.max(0, Round.DURATION - sinceStart)
  58. }
  59. }
  60. case class Information(text: String)
  61. case class Guess(username: String, text: String)
  62. case object Pass
  63. case class Taboo(username: String)
  64. case object End
  65. case class Score(kind: String, points: Int, card: Card, username: String = "")
  66. case object NextCard
  67. case object PrepRound
  68. case object StartRound
  69. case class EndRound(points: Int, card: Option[Card])
  70. class TabooGame(val chatActor: ActorRef) extends Actor {
  71. var ready = false
  72. var round: Option[Round] = None
  73. var roundActor: ActorRef = null
  74. val teamA: Team = new Team
  75. val teamB: Team = new Team
  76. var currentTeam: Team = teamB
  77. var opposingTeam: Team = teamA
  78. def receive = {
  79. case Join(username) =>
  80. if(teamA.size < 2 || teamA.size <= teamB.size) {
  81. teamA.join(username)
  82. }
  83. else {
  84. teamB.join(username)
  85. }
  86. announceStatus("join", username)
  87. if(round.isEmpty) {
  88. self ! PrepRound
  89. }
  90. case Quit(username) =>
  91. teamA.leave(username)
  92. teamB.leave(username)
  93. val lackingMembers = teamA.size < 2 || currentTeam == teamB && teamB.size < 2
  94. if(ready && lackingMembers) {
  95. ready = false
  96. }
  97. announceStatus("quit", username)
  98. if(!round.isEmpty && lackingMembers) {
  99. roundActor ! End
  100. }
  101. case Talk(username, "/status") => announceStatus()
  102. case Talk(username, text) => round match {
  103. case Some(round) =>
  104. if(username == round.team.player) text match {
  105. case "/pass" | "/p" => roundActor ! Pass
  106. case text => roundActor ! Information(text)
  107. }
  108. else if(round.team.guessers(username)) {
  109. roundActor ! Guess(username, text)
  110. }
  111. else if(round.monitors(username)) text match {
  112. case "/taboo" | "/t" => roundActor ! Taboo(username)
  113. }
  114. case None =>
  115. if(username == currentTeam.player) text match {
  116. case "/start" | "/s" => self ! StartRound
  117. }
  118. }
  119. case Score(kind, points, card, user) =>
  120. chatActor ! Announce(Json.obj(
  121. "kind" -> "point",
  122. "action" -> kind,
  123. "points" -> points,
  124. "card" -> card,
  125. "user" -> user
  126. ))
  127. self ! NextCard
  128. case NextCard =>
  129. val card = Card.getRandom()
  130. roundActor ! card
  131. val message = Json.obj(
  132. "kind" -> "card",
  133. "card" -> card
  134. )
  135. (round.get.monitors + player).foreach { user =>
  136. chatActor ! Tell(user, message)
  137. }
  138. case PrepRound =>
  139. if(!ready && teamA.size >= 2) {
  140. ready = true
  141. if(teamB.size < 2) {
  142. currentTeam = teamA
  143. opposingTeam = teamB
  144. }
  145. else {
  146. val temp = currentTeam
  147. currentTeam = opposingTeam
  148. opposingTeam = temp
  149. }
  150. currentTeam.nextPlayer()
  151. chatActor ! Announce(Json.obj(
  152. "kind" -> "roundReady",
  153. "player" -> currentTeam.player
  154. ))
  155. }
  156. case StartRound =>
  157. round = Some(Round(currentTeam, opposingTeam.members.toSet))
  158. roundActor = context.actorOf(Props[TabooRound])
  159. Akka.system.scheduler.scheduleOnce(1 minute, roundActor, End)
  160. chatActor ! Announce(Json.obj(
  161. "kind" -> "roundStart",
  162. "round" -> round
  163. ))
  164. self ! NextCard
  165. case EndRound(points, card) =>
  166. chatActor ! Announce(Json.obj(
  167. "kind" -> "roundEnd",
  168. "points" -> points,
  169. "card" -> card
  170. ))
  171. round = None
  172. ready = false
  173. self ! PrepRound
  174. }
  175. def player = round.get.team.player
  176. def announceStatus(kind: String = "status", user: String = "*GM") {
  177. chatActor ! Announce(Json.obj(
  178. "kind" -> kind,
  179. "user" -> user,
  180. "pendingPlayer" -> ifOpt(ready)(currentTeam.player),
  181. "round" -> round,
  182. "teamA" -> teamA,
  183. "teamB" -> teamB
  184. ))
  185. }
  186. self ! PrepRound
  187. }
  188. class TabooRound extends Actor {
  189. var card: Option[Card] = None
  190. var points = 0
  191. def receive = {
  192. case newCard: Card =>
  193. card = Some(newCard)
  194. case Guess(username, text) => card.map { card =>
  195. if(card.isCorrect(text)) {
  196. points += 1
  197. sender ! Score("correct", points, card, username)
  198. this.card = None
  199. }
  200. }
  201. case Information(text) => card.map { card =>
  202. if(card.isTaboo(text)) {
  203. points -= 1
  204. sender ! Score("invalid", points, card)
  205. this.card = None
  206. }
  207. }
  208. case Pass => card.map { card =>
  209. points -= 1
  210. sender ! Score("pass", points, card)
  211. this.card = None
  212. }
  213. case Taboo(username) => card.map { card =>
  214. points -= 1
  215. sender ! Score("taboo", points, card)
  216. this.card = None
  217. }
  218. case End =>
  219. sender ! EndRound(points, card)
  220. context.stop(self)
  221. }
  222. }