package actors import actors.UserActor.{BanMapMessage, GetName, HostLeaveLobby, LobbyFatal, Message, RefreshLobbyInfo, SecondPlayerLeaveLobby, SendBanMapMessage, SendMessage, SetUserAsHost, SetUserAsObs, SetUserAsSecondPlayer} import akka.actor.{Actor, ActorLogging, ActorRef} import akka.event.LoggingReceive import com.typesafe.scalalogging.LazyLogging import akka.pattern.ask import akka.util.Timeout import com.typesafe.config.{Config, ConfigFactory} import scala.collection.JavaConverters.iterableAsScalaIterableConverter import scala.collection.immutable.{HashSet, Queue} import scala.concurrent.Await import scala.concurrent.duration._ case class LobbyUser(name: String, actorRef: ActorRef) case class DeciderMap(map: String, var isBanned: Boolean = false) /** * There is one StockActor per stock symbol. The StockActor maintains a list of users watching the stock and the stock * values. Each StockActor updates a rolling dataset of randomly generated stock values. */ class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging { val config = ConfigFactory.load() logger.info(s"Create lobby... host: ${hostUser.actorRef.path.name}") private val host: LobbyUser = hostUser private var secondPlayer: Option[LobbyUser] = None private var firstPlayerReady: Boolean = false private var secondPlayerReady: Boolean = false private var playerTurn = 1 // all lobby users actors (observer and players) protected[this] var users: HashSet[ActorRef] = HashSet.empty[ActorRef] users = users + host.actorRef private var status: LobbyStatus = NotStarted() private var lobbyType: LobbyType = Last3() private val mapsLobby: Set[DeciderMap] = { val configMaps = config.getStringList("maps").asScala configMaps.map(e => { DeciderMap(e) }).toSet } hostUser.actorRef.tell( SetUserAsHost(LobbyInfo( UserInfo(hostUser.name,firstPlayerReady), UserInfo("", secondPlayerReady), self.path.name, status.toString(), playerTurn, lobbyType.toString(), mapsLobby)), this.self) implicit val timeout: Timeout = 1.second def receive = LoggingReceive { case BanMap(mapName: String) => // notify watchers logger.info(s"Ban map $mapName by ${sender.path.name}") if(status == Draft()){ if((playerTurn == 1 && sender == host.actorRef) || (playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))){ mapsLobby.find(p => p.map == mapName) match { case Some(map) => if(!map.isBanned){ map.isBanned = true if (playerTurn== 1) playerTurn = 2 else playerTurn = 1 if(mapsLobby.count(_.isBanned == false) == 3 && lobbyType == Last3() || mapsLobby.count(_.isBanned == false) == 4 && lobbyType == Last4() || mapsLobby.count(_.isBanned == false) == 1 && lobbyType == LooserPick()){ status = Finish() } users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) val senderName: String = if (sender == host.actorRef) { host.name } else secondPlayer.get.name users.foreach(_ ! SendBanMapMessage(BanMapMessage(senderName, mapName))) } else { logger.warn(s"User ban map $mapName, but map already banned") } case None => logger.error(s"Map $mapName not exist") } } else{ logger.warn(s"Player ${sender.path.name} ban map, but turn is $playerTurn") } } else { logger.warn(s"Player ${sender.path.name} ban map, but lobby status is '$status' status") } case SetReady => // notify watchers var readyPlayer = sender() if(readyPlayer == hostUser.actorRef){ firstPlayerReady = true } else if (secondPlayer.exists(sp => sp.actorRef == readyPlayer)) { secondPlayerReady = true } if(firstPlayerReady && secondPlayerReady){ status = Draft() } users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) case SetNotReady => // notify watchers var notReadyPlayer = sender() if(notReadyPlayer == hostUser.actorRef){ firstPlayerReady = false } else if (secondPlayer.exists(sp => sp.actorRef == notReadyPlayer)) { secondPlayerReady = false } users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) case ChangeLobbyType(lobbyTypeNew) => lobbyTypeNew match { case "last3" => lobbyType = Last3() case "last4" => lobbyType = Last4() case "looserpick" => lobbyType = LooserPick() } users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) case KickSecondPlayer => if (sender() == host.actorRef) { secondPlayer.foreach(player => player.actorRef ! LobbyFatal("You were kicked from lobby!")) secondPlayerReady = false secondPlayer = None users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) } case MessageForLobby(message) => if (sender() == host.actorRef) { users.foreach(_ ! SendMessage(Message(host.name, message))) }else if (secondPlayer.exists(sp => sp.actorRef == sender())){ users.foreach(_ ! SendMessage(Message(secondPlayer.get.name, message))) } case JoinLobbyAsPlayer(sp) => logger.info(s"User ${sender.path.name} join lobby ${self.path.name} as player") if (secondPlayer.isEmpty) { secondPlayer = Some(sp) users = users + sp.actorRef sp.actorRef.tell( SetUserAsSecondPlayer(getLobbyInfoResponse), this.self) users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) } else { sp.actorRef ! LobbyFatal("Lobby already full") } case WatchLobby(_) => // add the watcher to the list users = users + sender sender ! SetUserAsObs(getLobbyInfoResponse) case LeaveLobby => users = users - sender if(host.actorRef == sender()){ users.foreach(_ ! HostLeaveLobby) context.stop(self) }else if(secondPlayer.exists(_.actorRef == sender())){ users.foreach(_ ! SecondPlayerLeaveLobby) secondPlayer = None context.stop(self) } if (users.isEmpty) { logger.info(s"Stop lobby ${self.path.name}") context.stop(self) } case InfoQuery => logger.info(s"Info query for lobby ${self.path.name} and host ${host.actorRef.path.name}") sender ! RefreshLobbyInfo(getLobbyInfoResponse) } private def getLobbyInfoResponse: LobbyInfo = { val user2Name = secondPlayer.map(_.name).getOrElse("") LobbyInfo( UserInfo(host.name, firstPlayerReady), UserInfo(user2Name, secondPlayerReady), self.path.name, status.toString(), playerTurn, lobbyType.toString(), mapsLobby) } } case class BanMap(mapName: String) case class MapsUpdate(maps: Set[String]) case class CreateLobby(userName: String) case class JoinLobbyAsPlayer(lobbyUser: LobbyUser) case object KickSecondPlayer case class WatchLobby(lobbyName: String) case object LeaveLobby case object SetReady case object SetNotReady case class MessageForLobby(message: String) case class ChangeLobbyType(lobbyType: String) case object InfoQuery case class UserInfo(name: String, isReady: Boolean) case class LobbyInfo(user1Info: UserInfo, user2Info: UserInfo, lobbyActorName: String, status: String, playerTurn: BigDecimal, selectedType: String, maps: Set[DeciderMap]) class LobbyStatus sealed case class NotStarted() extends LobbyStatus sealed case class Draft() extends LobbyStatus sealed case class Finish() extends LobbyStatus class LobbyType sealed case class Last3() extends LobbyType sealed case class Last4() extends LobbyType sealed case class LooserPick() extends LobbyType