Taboo.scala 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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 = 120
  42. val GRACE_PERIOD = 2
  43. implicit val writes = new Writes[Round] {
  44. def writes(o: Round) = Json.obj(
  45. "team" -> o.team,
  46. "monitors" -> o.monitors,
  47. "remainingTime" -> o.remainingTime
  48. )
  49. }
  50. }
  51. case class Round(
  52. team: Team,
  53. monitors: Set[String],
  54. startTime: Long = System.currentTimeMillis
  55. ) {
  56. def remainingTime = {
  57. val sinceStart = (System.currentTimeMillis - startTime) / 1000
  58. Math.max(0, Round.DURATION - sinceStart)
  59. }
  60. }
  61. case class Information(text: String)
  62. case class Guess(username: String, text: String)
  63. case object Pass
  64. case class Correct(username: String)
  65. case class Taboo(username: String)
  66. case object End
  67. case class Abuse(kind: String, username: String)
  68. case class Score(kind: String, points: Int, card: Card, username: String = "")
  69. case object NextCard
  70. case object PrepRound
  71. case object StartRound
  72. case class EndRound(points: Int, card: Option[Card])
  73. class TabooGame(val chatActor: ActorRef) extends Actor {
  74. var ready = false
  75. var round: Option[Round] = None
  76. var roundActor: ActorRef = null
  77. val teamA: Team = new Team
  78. val teamB: Team = new Team
  79. var currentTeam: Team = teamB
  80. var opposingTeam: Team = teamA
  81. var cardPool: CardPool = CardPool.get()
  82. def receive = {
  83. case Join(username) =>
  84. if(teamA.size < 2 || teamA.size <= teamB.size) {
  85. teamA.join(username)
  86. }
  87. else {
  88. teamB.join(username)
  89. }
  90. announceStatus("join", username)
  91. if(round.isEmpty) {
  92. self ! PrepRound
  93. }
  94. case Quit(username) =>
  95. teamA.leave(username)
  96. teamB.leave(username)
  97. val lackingMembers = teamA.size < 2 || currentTeam == teamB && teamB.size < 2
  98. if(ready && lackingMembers) {
  99. ready = false
  100. }
  101. announceStatus("quit", username)
  102. if(!round.isEmpty && lackingMembers) {
  103. roundActor ! End
  104. }
  105. case Talk(username, "/status") => announceStatus()
  106. case Talk(username, text) => round match {
  107. case Some(round) =>
  108. if(username == round.team.player) text match {
  109. case "/pass" | "/p" => roundActor ! Pass
  110. case "/correct" | "/c" => roundActor ! Correct(username)
  111. case text => roundActor ! Information(text)
  112. }
  113. else if(round.team.guessers(username)) {
  114. roundActor ! Guess(username, text)
  115. }
  116. else if(round.monitors(username)) text match {
  117. case "/taboo" | "/t" => roundActor ! Taboo(username)
  118. case "/correct" | "/c" => roundActor ! Correct(username)
  119. case _ => Unit
  120. }
  121. case None =>
  122. if(username == currentTeam.player) text match {
  123. case "/start" | "/s" => self ! StartRound
  124. case _ => Unit
  125. }
  126. }
  127. case Score(kind, points, card, user) =>
  128. chatActor ! Announce(Json.obj(
  129. "kind" -> "point",
  130. "action" -> kind,
  131. "points" -> points,
  132. "card" -> card,
  133. "user" -> user
  134. ))
  135. self ! NextCard
  136. case Abuse(kind, user) =>
  137. chatActor ! Announce(Json.obj(
  138. "kind" -> "abuse",
  139. "action" -> kind,
  140. "user" -> user
  141. ))
  142. case NextCard =>
  143. if(!cardPool.hasNext) {
  144. cardPool = CardPool.get()
  145. chatActor ! Announce(Json.obj(
  146. "kind" -> "talk",
  147. "user" -> "*GM",
  148. "message" -> "And we're out of cards... Reshuffling the current set. You can help by contributing more cards though! *hint* *hint*"
  149. ))
  150. }
  151. val card = cardPool.next()
  152. roundActor ! card
  153. val message = Json.obj(
  154. "kind" -> "card",
  155. "card" -> card
  156. )
  157. (round.get.monitors + player).foreach { user =>
  158. chatActor ! Tell(user, message)
  159. }
  160. case PrepRound =>
  161. if(!ready && teamA.size >= 2) {
  162. ready = true
  163. if(teamB.size < 2) {
  164. currentTeam = teamA
  165. opposingTeam = teamB
  166. }
  167. else {
  168. val temp = currentTeam
  169. currentTeam = opposingTeam
  170. opposingTeam = temp
  171. }
  172. currentTeam.nextPlayer()
  173. chatActor ! Announce(Json.obj(
  174. "kind" -> "roundReady",
  175. "player" -> currentTeam.player
  176. ))
  177. }
  178. case StartRound =>
  179. round = Some(Round(currentTeam, opposingTeam.members.toSet))
  180. roundActor = context.actorOf(Props[TabooRound])
  181. Akka.system.scheduler.scheduleOnce(Round.DURATION seconds, roundActor, End)
  182. chatActor ! Announce(Json.obj(
  183. "kind" -> "roundStart",
  184. "round" -> round
  185. ))
  186. self ! NextCard
  187. case EndRound(points, card) =>
  188. chatActor ! Announce(Json.obj(
  189. "kind" -> "roundEnd",
  190. "points" -> points,
  191. "card" -> card
  192. ))
  193. round = None
  194. ready = false
  195. self ! PrepRound
  196. }
  197. def player = round.get.team.player
  198. def announceStatus(kind: String = "status", user: String = "*GM") {
  199. chatActor ! Announce(Json.obj(
  200. "kind" -> kind,
  201. "user" -> user,
  202. "pendingPlayer" -> ifOpt(ready)(currentTeam.player),
  203. "round" -> round,
  204. "teamA" -> teamA,
  205. "teamB" -> teamB
  206. ))
  207. }
  208. self ! PrepRound
  209. }
  210. class TabooRound extends Actor {
  211. var card: Option[Card] = None
  212. var points = 0
  213. var guesses = 0
  214. var infos = 0
  215. var graceEnd = 0L
  216. def receive = {
  217. case newCard: Card =>
  218. card = Some(newCard)
  219. guesses = 0
  220. infos = 0
  221. graceEnd = System.currentTimeMillis + Round.GRACE_PERIOD * 1000
  222. case End =>
  223. sender ! EndRound(points, card)
  224. context.stop(self)
  225. case other => card.map { card =>
  226. other match {
  227. case Guess(username, text) =>
  228. guesses += 1
  229. if(card.isCorrect(text)) {
  230. point(1, "correct", username)
  231. }
  232. case Information(text) =>
  233. infos += 1
  234. if(card.isTaboo(text) && !inLeeway) {
  235. point(-1, "invalid")
  236. }
  237. case Pass => point(-1, "pass")
  238. case Correct(username) =>
  239. if(guesses == 0) {
  240. abuse("correctp", username)
  241. }
  242. else if(!inLeeway) {
  243. point(1, "correctp", username)
  244. }
  245. case Taboo(username) =>
  246. if(infos == 0) {
  247. abuse("taboo", username)
  248. }
  249. else if(!inLeeway) {
  250. point(-1, "taboo", username)
  251. }
  252. }
  253. }
  254. }
  255. def inLeeway = System.currentTimeMillis < graceEnd
  256. def abuse(kind: String, username: String) = {
  257. sender ! Abuse(kind, username)
  258. }
  259. def point(pointDiff: Int, kind: String, username: String = "") = card.map { card =>
  260. points += pointDiff
  261. sender ! Score(kind, points, card, username)
  262. this.card = None
  263. }
  264. }