diff --git a/app/actors/LobbieActor.scala b/app/actors/LobbieActor.scala index 11506de..4170759 100644 --- a/app/actors/LobbieActor.scala +++ b/app/actors/LobbieActor.scala @@ -1,74 +1,159 @@ package actors +import actors.UserActor.{GetName, HostLeaveLobby, RefreshLobbyInfo, SetUserAsHost} 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 DeciderMap(map: String, isBanned: Boolean = false) +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(lobbyName: String, hostUser: ActorRef) extends Actor with LazyLogging { +class LobbieActor(hostUser: LobbyUser) extends Actor with LazyLogging { - private val name: String = lobbyName + val config = ConfigFactory.load() + logger.info(s"Create lobby... host: ${hostUser.actorRef.path.name}") - // user actors + + 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] - private var status = NotStarted + users = users + host.actorRef - private var maps: List[DeciderMap] = List(DeciderMap("map1"), DeciderMap("map2"), DeciderMap("map3")) + private var status: LobbyStatus = NotStarted() - private var secondPlayer: Option[ActorRef] = None + private val mapsLobby: Set[DeciderMap] = { + val configMaps = config.getStringList("maps").asScala + configMaps.map(e => { + DeciderMap(e) + }).toSet + } - logger.info(s"Create lobby with name $lobbyName") + hostUser.actorRef.tell( + SetUserAsHost(LobbyInfo(hostUser.name, "", firstPlayerReady, secondPlayerReady, self.path.name, status.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}") - users.foreach(_ ! MapsUpdate( maps)) - case JoinLobbyAsPlayer(_) => - secondPlayer = Some(sender) + if(status == Draft()){ + if((playerTurn == 1 && sender == host.actorRef) || + (playerTurn == 2 && secondPlayer.exists(_.actorRef == sender))){ + mapsLobby.find(p => p.map == mapName).foreach(_.isBanned = true) + if (playerTurn== 1) playerTurn = 2 else playerTurn = 1 + if(mapsLobby.count(_.isBanned == false) == 1){ + status = Finish() + } + users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) + } 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 it 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 JoinLobbyAsPlayer(sp) => + logger.info(s"User ${sender.path.name} join lobby ${self.path.name} as player") + secondPlayer = Some(sp) + users = users + sp.actorRef + sp.actorRef.tell( + SetUserAsHost(LobbyInfo(hostUser.name, sp.name, firstPlayerReady, secondPlayerReady, self.path.name, status.toString(), mapsLobby)), this.self) + users.foreach(_ ! RefreshLobbyInfo(getLobbyInfoResponse)) case WatchLobby(_) => // add the watcher to the list users = users + sender case LeaveLobby => users = users - sender - if(secondPlayer.contains(sender)) secondPlayer = None + if(secondPlayer.exists(sp => sp.actorRef == sender)) secondPlayer = None + if(host.actorRef == sender()){ + users.foreach(_ ! HostLeaveLobby) + } if (users.isEmpty) { - logger.info(s"Stop lobby with name: $name") + logger.info(s"Stop lobby ${self.path.name}") context.stop(self) } case InfoQuery => - sender ! LobbyInfoResponse(name, self.path.name, status.toString()) + 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(host.name, user2Name, firstPlayerReady, secondPlayerReady, self.path.name, status.toString(), mapsLobby) } } - case class BanMap(mapName: String) -case class MapsUpdate(maps: List[DeciderMap]) - -case class MapsHistory(symbol: String, maps: List[DeciderMap]) +case class MapsUpdate(maps: Set[String]) case class CreateLobby(userName: String) -case class JoinLobbyAsPlayer(lobbyName: String) - case class WatchLobby(lobbyName: String) -case class LeaveLobby(lobbyName: String) +case class JoinLobbyAsPlayer(lobbyUser: LobbyUser) + +case object LeaveLobby + +case object SetReady + +case object SetNotReady case object InfoQuery -case class LobbyInfoResponse(lobbyName: String, lobbyActorName: String, status: String) +case class LobbyInfo(user1Name: String, + user2Name: String, + user1Ready: Boolean, + user2Ready: Boolean, + lobbyActorName: String, + status: String, + maps: Set[DeciderMap]) class LobbyStatus diff --git a/app/actors/LobbiesActor.scala b/app/actors/LobbiesActor.scala index 0b70406..bb0e0c0 100644 --- a/app/actors/LobbiesActor.scala +++ b/app/actors/LobbiesActor.scala @@ -16,23 +16,26 @@ class LobbiesActor extends Actor with LazyLogging { val lobbies: ListBuffer[String] = ListBuffer() - def receive = LoggingReceive { - case CreateLobby(userName) => - val lobbyActor = context.actorOf(Props(new LobbieActor(userName, sender)), + def receive: Receive = LoggingReceive { + case CreateLobby(hostName) => + val hostActorRef = sender + logger.info(s"Player ${hostActorRef.path.name} create lobby.") + val lobbyActor = context.actorOf(Props(new LobbieActor(LobbyUser(hostName,hostActorRef))), s"lobbyActor-${(math.random*100000000L).toLong}") - lobbyActor.tell(WatchLobby("watchIt"), sender) + lobbyActor.tell(WatchLobby("watchIt"), hostActorRef) + case JoinLobbyByActorName(lobbyName, userName) => + // get or create the StockActor for the symbol and forward this message + val user = sender + context.child(lobbyName) match { + case Some(lobbyActor) => lobbyActor ! JoinLobbyAsPlayer(LobbyUser(userName, user)) + case None => logger.error(s"Can't watch lobby $lobbyName - lobby not exists") + } case watchLobby@WatchLobby(lobbyName) => // get or create the StockActor for the symbol and forward this message context.child(lobbyName) match { case Some(lobbyActor) => lobbyActor forward watchLobby case None => logger.error(s"Can't watch lobby $lobbyName - lobby not exists") } - case unwatchLobby@LeaveLobby(symbol) => - // if there is a StockActor for the symbol forward this message - context.child(symbol).foreach(_.forward(unwatchLobby)) - case banMap@BanMap(map) => - logger.info(s"forvard ban $map") - context.children.foreach(_.forward(banMap)) case UnWatchAllLobbies => context.children.foreach( _.tell(LeaveLobby, sender)) case GetAllLobbies => @@ -45,4 +48,6 @@ class LobbiesActor extends Actor with LazyLogging { case object GetAllLobbies +case class JoinLobbyByActorName(actorName: String, userName: String) + case object UnWatchAllLobbies \ No newline at end of file diff --git a/app/actors/UserActor.scala b/app/actors/UserActor.scala index 12c2848..232f421 100644 --- a/app/actors/UserActor.scala +++ b/app/actors/UserActor.scala @@ -1,6 +1,6 @@ package actors -import actors.UserActor.GetName +import actors.UserActor.{GetName, HostLeaveLobby, RefreshLobbyInfo, SetUserAsHost, SetUserAsSecondPlayer} import akka.actor._ import akka.event.LoggingReceive import akka.pattern.ask @@ -18,16 +18,7 @@ class UserActor(out: ActorRef, implicit val timeout: Timeout = 3.seconds - var userStatus: UserStatus = InLobby() - - implicit val lobbyResponseWrites: Writes[List[LobbyInfoResponse]] = new Writes[List[LobbyInfoResponse]] { - - implicit val lobbyWrites: OWrites[LobbyInfoResponse] = Json.writes[LobbyInfoResponse] - - override def writes(o: List[LobbyInfoResponse]): JsValue = { - JsArray(o.map(lobby => Json.toJson(lobby))) - } - } + var lobbieActor: Option[ActorRef] = None implicit val mapJson: Writes[DeciderMap] = new Writes[DeciderMap] { def writes(deciderMap: DeciderMap): JsValue = { @@ -38,6 +29,15 @@ class UserActor(out: ActorRef, } } + implicit val lobbyWrites: OWrites[LobbyInfo] = Json.writes[LobbyInfo] + + implicit val lobbyResponseWrites: Writes[List[LobbyInfo]] = new Writes[List[LobbyInfo]] { + + override def writes(o: List[LobbyInfo]): JsValue = { + JsArray(o.map(lobby => Json.toJson(lobby))) + } + } + var name = "" override def preStart(): Unit = { @@ -52,14 +52,29 @@ class UserActor(out: ActorRef, override def receive: Receive = LoggingReceive { - case MapsUpdate(deciderMaps) => - val maps = deciderMaps.map(map => Json.toJson(map)) - out ! Json.obj("type" -> "maps", "deciderMaps" -> maps) case GetName => sender ! name + case SetUserAsHost(lobbyInfo) => + val lobbyActor = sender() + logger.info(s"Receive set user ${self.path.name} as host from ${lobbyActor.path.name}") + setUserAsJoinedOrCreatedLobby(lobbyActor, lobbyInfo) + + case SetUserAsSecondPlayer(lobbyInfo) => + val lobbyActor = sender() + logger.info(s"Receive set user ${self.path.name} as second player from ${lobbyActor.path.name}") + setUserAsJoinedOrCreatedLobby(lobbyActor, lobbyInfo) + + case HostLeaveLobby => + logger.info(s"Host leave from lobby ${sender.path.name}") + out ! Json.obj("type" -> "hostLeaveLobby") + + case RefreshLobbyInfo(lobbyInfo) => + logger.trace(s"Refresh lobby info: $lobbyInfo") + out ! Json.obj("type" -> "refreshLobby", "lobby" -> lobbyInfo) + case json: JsValue => - // When the user types in a stock in the upper right corner, this is triggered + // Обрабатываем запросы с фронта val comType = (json \ "type").asOpt[String] comType match { @@ -67,8 +82,8 @@ class UserActor(out: ActorRef, name = (json \ "name").as[String] logger.debug(s"Set user name: $name for actor ${this.self}") case Some("getAllUsers") => - val usersActorFuture = (userParentActor ? UserParentActor.GetAllUsers).mapTo[Iterable[ActorRef]] - usersActorFuture.map(actorRefs => { + val userActorsFuture = (userParentActor ? UserParentActor.GetAllUsers).mapTo[Iterable[ActorRef]] + userActorsFuture.map(actorRefs => { logger.debug(s"There are ${actorRefs.size} users on site") actorRefs.map(userActorRef => userActorRef ? GetName).map(res => { res.onComplete { @@ -81,23 +96,41 @@ class UserActor(out: ActorRef, lobbiesActor ! CreateLobby(name) case Some("leaveDecider") => - lobbiesActor ! LeaveLobby(name) + lobbieActor.foreach(lobby => lobby ! LeaveLobby) - case Some("updateDecider") => - lobbiesActor ! BanMap("map1") + case Some("setReady") => + lobbieActor.foreach(lobby => lobby ! SetReady) + + case Some("setNotReady") => + lobbieActor.foreach(lobby => lobby ! SetNotReady) + + case Some("joinDecider") => + val lobbyActorName = (json \ "lobbyActorName").as[String] + logger.info(s"Player ${self.path.name} join lobby $lobbyActorName") + lobbiesActor ! JoinLobbyByActorName(lobbyActorName, name) + + case Some("banMap") => + val map = (json \ "map").as[String] + lobbieActor.foreach(lobby => lobby ! BanMap(map)) case Some("getLobbies") => logger.debug("Get all lobby request") - (lobbiesActor ? GetAllLobbies).mapTo[List[LobbyInfoResponse]] onComplete { + (lobbiesActor ? GetAllLobbies).mapTo[List[RefreshLobbyInfo]] onComplete { case Success(lobbies) => { logger.info(s"Received lobbies: $lobbies") - out ! Json.obj("type" -> "lobbies", "lobbies" -> lobbies) + out ! Json.obj("type" -> "lobbies", "lobbies" -> lobbies.map(res => res.lobbyInfo)) } case Failure(ex) => logger.error("Received error", ex) } } } + + private def setUserAsJoinedOrCreatedLobby(lobbyActor: ActorRef, lobbyInfo: LobbyInfo): Unit = { + this.lobbieActor = Some(lobbyActor) + out ! Json.obj("type" -> "switchToLobby", "lobby" -> lobbyInfo) + } + } class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging { @@ -113,14 +146,6 @@ class UserParentActor(actorSystem: ActorSystem) extends Actor with ActorLogging } } -class UserStatus - -case class InLobby() extends UserStatus - -case class NotReady() extends UserStatus - -case class Ready() extends UserStatus - object UserParentActor { @@ -139,7 +164,16 @@ object UserActor { case object GetName + case class RefreshLobbyInfo(lobbyInfo: LobbyInfo) + + case class SetUserAsHost(lobbyInfo: LobbyInfo) + + case class SetUserAsSecondPlayer(lobbyInfo: LobbyInfo) + case object CreateLobby + case object HostLeaveLobby + + } diff --git a/app/assets/images/buttons/isNotAuto.png b/app/assets/images/buttons/isNotAuto.png new file mode 100644 index 0000000..391f7e6 Binary files /dev/null and b/app/assets/images/buttons/isNotAuto.png differ diff --git a/app/assets/images/maps/2p_blood_river_[Rem].jpg b/app/assets/images/maps/2p_blood_river_[Rem].jpg new file mode 100644 index 0000000..776f5d1 Binary files /dev/null and b/app/assets/images/maps/2p_blood_river_[Rem].jpg differ diff --git a/app/assets/images/maps/2p_fata_morgana_[Rem].jpg b/app/assets/images/maps/2p_fata_morgana_[Rem].jpg new file mode 100644 index 0000000..d086ea2 Binary files /dev/null and b/app/assets/images/maps/2p_fata_morgana_[Rem].jpg differ diff --git a/app/assets/javascripts/index.js b/app/assets/javascripts/index.js index 244ee75..d6f15d1 100644 --- a/app/assets/javascripts/index.js +++ b/app/assets/javascripts/index.js @@ -1,8 +1,7 @@ - -var updateDecider; - var ws; +var userName = "" + window.onload = function() { 'use strict'; @@ -11,36 +10,6 @@ window.onload = function() { return Math.floor(Math.random() * Math.floor(max)); } - var DeciderLobby = Backbone.View.extend({ - - lobbies: undefined, - - initialize: function (number) { - console.log(number); - this.elCount = number; - }, - - template: _.template('<%= title %>' + - '<%= title %>' + - '<%= content %>'), - - events: { - "click .title": "check" // Обработчик клика на кнопке "Проверить" - }, - - check: function () { - this.elCount = "azaz" + getRandomInt(100000000); - this.render(); - }, - - render: function() { - console.log("render"); - this.$el.html(this.template({title: "Vasyan " + this.elCount, content: "noob"})); - console.log(this.$el); - return this; - } - }); - var LobbyList = Backbone.View.extend({ lobbies: undefined, @@ -59,28 +28,50 @@ window.onload = function() { "click .title": "check" // Обработчик клика на кнопке "Проверить" }, - check: function () { + joinAsPlayer: function () { this.elCount = "azaz" + getRandomInt(100000000); this.render(); }, render: function() { - this.$el.html(this.template()); + var lobbiesAsTableRows = _.reduce(this.lobbies, function (memo, lobby) { - console.log(lobby, memo); + + var joinButton = ''; + var observerButton = ''; + return memo + '\n' + - ' '+ lobby.lobbyName +'\n' + + ' '+ lobby.user1Name +'\n' + + ' '+ lobby.user2Name +'\n' + ' '+ lobby.status +'\n' + - ' Action' + + ' ' + joinButton + ' ' + observerButton + + ' ' + ''; }, ""); - $("#deciderList tbody", this.el).append(lobbiesAsTableRows); - return this; + + var tableStart = '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n'; + + var tableEnd = '
Player1Player2StatusAction
'; + + var res = tableStart + lobbiesAsTableRows + tableEnd; + + $("#deciderList tbody", res).append(lobbiesAsTableRows); + console.log(res); + + this.$el.html(res); } }); var lobbyList = new LobbyList; - $("#lobbiesList").append(lobbyList.el); + $("#lobbiesList").append(lobbyList.$el); lobbyList.render(); $("#createDecider").click(function(event) { @@ -104,25 +95,45 @@ window.onload = function() { })); }); + $("#exit").click(function(event) { + event.preventDefault(); + ws.send(JSON.stringify({ + type: "leaveDecider" + })); + $("#decider").hide(); + $("#lobbies").show(); + }); + if($.cookie('user')=== undefined){ - result = prompt("Введите ник", ); + var result = prompt("Введите ник", ); $.cookie('user', result, { expires: 7 }); + userName = result; }else{ - console.log("Welcome, " + $.cookie('user')); + userName = $.cookie('user'); } + $("#playerName").html(userName); + + ws = new WebSocket($("body").data("ws-url")); ws.onmessage = function(event) { var message; message = JSON.parse(event.data); switch (message.type) { - case "maps": - return updateDecider(message); - default: + case "refreshLobby": + renderMaps(message.lobby.maps); + renderPlayersAndStats(message.lobby); + break; + case "switchToLobby": + console.log(message); + switchToLobby(message.lobby.maps); + renderPlayersAndStats(message.lobby); + break; case "lobbies": lobbyList.lobbies = message.lobbies; lobbyList.render(); return console.log(message); + default: } }; ws.onopen = function () { @@ -140,15 +151,144 @@ window.onload = function() { function changeNick() { result = prompt("Введите ник", $.cookie('user')); + if(result == null) return; $.cookie('user', result, { expires: 7 }); ws.send(JSON.stringify({ type: "userName", name: $.cookie('user') })); + + userName = $.cookie('user'); + $("#playerName").html(userName); + } -updateDecider = function(message) { - console.log(message.deciderMaps); -}; +// -----ф-ии прихода с сервера +function switchToLobby(maps) { + console.log(maps); + renderMaps(maps); + $("#lobbies").hide(); + $("#decider").show(); +} + +function renderMaps(maps) { + var resHtml = ""; + + _.each(maps, function (map) { + + var banHtml = ""; + var banClass = ""; + if(map.isBanned){ + banHtml = "
"; + banClass = "bannedMap"; + } + + + + resHtml = resHtml + '
'; + }) + $("#mapList").html(resHtml); +} + +function renderPlayersAndStats(lobby) { + var resHtml = "
"; + var player1ReadyBtn = ""; + var player2ReadyBtn = ""; + var readyImg = ""; + var notReadyImg = ""; + + if(lobby.user1Name === userName){ + if(lobby.user1Ready){ + player1ReadyBtn = "" + }else{ + player1ReadyBtn = "" + } + }else{ + if(lobby.user1Ready){ + player1ReadyBtn = readyImg + }else{ + player1ReadyBtn = notReadyImg + } + } + + if(lobby.user2Name === userName){ + if(lobby.user2Ready){ + player2ReadyBtn = "" + }else{ + player2ReadyBtn = "" + } + } else { + if(lobby.user2Ready){ + player2ReadyBtn = readyImg + }else{ + player2ReadyBtn = notReadyImg + } + } + + console.log(lobby); + + switch (lobby.status){ + case "NotStarted()": + resHtml = "
" + lobby.user1Name + ": " + player1ReadyBtn +"
" + if(lobby.user2Name !== ""){ + var kickBtn = "" + if(lobby.user1Name === userName){ + kickBtn = ""; + } + resHtml += "
" + lobby.user2Name + ": " + player2ReadyBtn + "
" + kickBtn + "
" + }else{ + resHtml += "
waiting 2-nd player...
" + } + break; + case "Draft()": + resHtml = "
"+lobby.user1Name+ " vs " + lobby.user2Name +"
"; + break; + case "Finish()": + var lastMap = _.find(lobby.maps, function (map){ + return map.isBanned === false; + }).map; + resHtml = "
"+convertMapName(lastMap)+"
"; + } + + resHtml = resHtml + "
" + + $("#playersStatsList").html(resHtml); +} + +function banMap(map){ + ws.send(JSON.stringify({ + type: "banMap", + map: map + })); +} + +function setReady(){ + ws.send(JSON.stringify({ + type: "setReady" + })); +} + +function setNotReady(){ + ws.send(JSON.stringify({ + type: "setNotReady" + })); +} + +function joinDecider(actorName){ + console.log(actorName); + ws.send(JSON.stringify({ + type: "joinDecider", + lobbyActorName: actorName + })); +} + +function convertMapName (techMapName) { + var mapName = techMapName.replace("2p_", "").replaceAll("_", " ") + " (2)"; + return mapName.charAt(0).toUpperCase() + mapName.slice(1); +} diff --git a/app/assets/stylesheets/main.less b/app/assets/stylesheets/main.less index c8fa81c..d462e46 100644 --- a/app/assets/stylesheets/main.less +++ b/app/assets/stylesheets/main.less @@ -67,6 +67,36 @@ body { } } +.readyImg{ + height: 25px; +} + +#playerName{ + color: white; +} + +.banX{ + font-size: 128px; + margin-top: -174px; + left: 34px; + color: red; + position: absolute; + z-index: 1; +} + +.bannedMap{ + -webkit-filter: grayscale(1); /* Webkit браузеры */ +} + +.mapSelect{ + margin: 10px; + position:relative; + width: 175px; + height: 175px; + white-space: nowrap; + overflow: hidden; +} + .chart-holder, .details-holder { position: absolute; width: 100%; diff --git a/app/views/index.scala.html b/app/views/index.scala.html index 953afe4..2164667 100644 --- a/app/views/index.scala.html +++ b/app/views/index.scala.html @@ -17,27 +17,28 @@