package actors import actors.UserActor._ import akka.actor.{Actor, ActorRef} import akka.event.LoggingReceive import akka.util.Timeout import com.typesafe.config.ConfigFactory import com.typesafe.scalalogging.LazyLogging import java.util import scala.collection.JavaConverters.iterableAsScalaIterableConverter import scala.collection.immutable.HashSet import scala.concurrent.duration._ import scala.util.Try case class LobbyUser(name: String, actorRef: ActorRef) case class DeciderMap(map: String, description: Option[String] = None, 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, deciderName: String) 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 = Last1() private var firstPlayerSelectedRaces: Option[SelectedRaces] = None private var secondPlayerSelectedRaces: Option[SelectedRaces] = None private val isSolo: Boolean = Try(config.getBoolean(s"deciders.$deciderName.isSolo")).getOrElse(false) private val mapsLobby: Set[DeciderMap] = { val configMaps = config.getList(s"deciders.$deciderName.maps").asScala configMaps.map(e => { val mapConfig = e.unwrapped().asInstanceOf[util.ArrayList[String]].asScala DeciderMap(mapConfig.head, mapConfig.tail.headOption) }).toSet } hostUser.actorRef.tell( SetUserAsHost(LobbyInfo( UserInfo(hostUser.name, firstPlayerReady), UserInfo("", secondPlayerReady), self.path.name, deciderName, status.toString(), playerTurn, lobbyType.toString(), isSolo, 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 (isSolo) { mapsLobby.find(p => p.map == mapName).foreach(m => m.isBanned = true) if (isFinish) { status = Finish() } else { status = Draft() } refreshAndBanMap(mapName) } 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 (isFinish) { status = Finish() } refreshAndBanMap(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 selectedRaces: SelectedRaces => if (sender == host.actorRef) { firstPlayerSelectedRaces = Some(selectedRaces) logger.info(s"First player select $selectedRaces") } else if (sender == secondPlayer.get.actorRef) { secondPlayerSelectedRaces = Some(selectedRaces) logger.info(s"Second player select $selectedRaces") } if (firstPlayerSelectedRaces.nonEmpty && secondPlayerSelectedRaces.nonEmpty) { status = Draft() users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) } 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 = SelectRace() } 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 "last1" => lobbyType = Last1() case "last3" => lobbyType = Last3() case "last5" => lobbyType = Last5() case "last7" => lobbyType = Last7() } 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())) { secondPlayerReady = false secondPlayer = None if (status == Draft() || status == SelectRace()) { users.foreach(_ ! SecondPlayerLeaveLobby) context.stop(self) } else { users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) } } 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, firstPlayerSelectedRaces), UserInfo(user2Name, secondPlayerReady, secondPlayerSelectedRaces), self.path.name, deciderName, status.toString(), playerTurn, lobbyType.toString(), isSolo, mapsLobby) } private def isFinish: Boolean = { mapsLobby.count(_.isBanned == false) == 1 && lobbyType == Last1() || mapsLobby.count(_.isBanned == false) == 3 && lobbyType == Last3() || mapsLobby.count(_.isBanned == false) == 5 && lobbyType == Last5() || mapsLobby.count(_.isBanned == false) == 7 && lobbyType == Last7() } private def refreshAndBanMap(mapName: String): Unit = { val senderName: String = if (sender == host.actorRef) { host.name } else secondPlayer.get.name users.foreach(_ ! SendBanMapMessage(BanMapMessage(senderName, mapName))) users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) } } case class BanMap(mapName: String) case class MapsUpdate(maps: Set[String]) case class CreateLobby(userName: String, deciderName: 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 class ChangeIsNecronSelected(isSelected: Boolean) case object InfoQuery case class SelectedRaces(firstRace: Int, secondRace: Int, thirdRace: Int, fourthRace: Int) case class UserInfo(name: String, isReady: Boolean, selectedRaces: Option[SelectedRaces] = None) case class LobbyInfo(user1Info: UserInfo, user2Info: UserInfo, lobbyActorName: String, deciderName: String, status: String, playerTurn: BigDecimal, selectedType: String, isSolo: Boolean, maps: Set[DeciderMap]) class LobbyStatus sealed case class NotStarted() extends LobbyStatus sealed case class SelectRace() extends LobbyStatus sealed case class Draft() extends LobbyStatus sealed case class Finish() extends LobbyStatus class LobbyType sealed case class Last1() extends LobbyType sealed case class Last3() extends LobbyType sealed case class Last5() extends LobbyType sealed case class Last7() extends LobbyType sealed case class Superfinal() extends LobbyType