From 87414dafaefd24cb4ddd811215ba8c876bf6e26f Mon Sep 17 00:00:00 2001 From: Peter Janssen Date: Fri, 21 Sep 2018 17:02:52 +0200 Subject: [PATCH] Add start endpoint to run concurrent games --- .../src/it/scala/server/IntegrationTest.scala | 12 ++++++- server/src/main/scala/server/Main.scala | 14 ++------- server/src/main/scala/server/Server.scala | 24 ++++++++++++-- .../main/scala/server/actors/GameActor.scala | 18 ++++++++--- .../main/scala/util/TwitterConverters.scala | 31 +++++++++++++++++++ 5 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 server/src/main/scala/util/TwitterConverters.scala diff --git a/server/src/it/scala/server/IntegrationTest.scala b/server/src/it/scala/server/IntegrationTest.scala index 3bde03f..3eed28f 100644 --- a/server/src/it/scala/server/IntegrationTest.scala +++ b/server/src/it/scala/server/IntegrationTest.scala @@ -12,7 +12,6 @@ class IntegrationTest extends FunSuite with BeforeAndAfterAll with Matchers with private val port = 8080 private val serverUrl: String = s"localhost:$port" - private val gamesUrl: String = s"http://$serverUrl/games" var server: Server = _ @@ -36,6 +35,16 @@ class IntegrationTest extends FunSuite with BeforeAndAfterAll with Matchers with result shouldBe expected } + test("start new game on post to /start"){ + val expected = "started" + + val requests = (1 to 1000) map { _ => + sendPost(Json.Null, s"http://$serverUrl/start") + } + + val r = Await.result(Future.join(requests)) + } + lazy val client: Service[http.Request, http.Response] = Http.newService(serverUrl) def sendGet(uri: String): Future[Response] = { @@ -45,6 +54,7 @@ class IntegrationTest extends FunSuite with BeforeAndAfterAll with Matchers with def sendPost(payload: Json, url: String): Future[Response] = { val bytes = Buf.ByteArray(payload.toString().getBytes(): _*) +// val request = http.RequestBuilder().buildPost(bytes) todo why doesn't this work val request = RequestBuilder().url(url).buildPost(bytes) client(request) } diff --git a/server/src/main/scala/server/Main.scala b/server/src/main/scala/server/Main.scala index eee4dcb..a70572f 100644 --- a/server/src/main/scala/server/Main.scala +++ b/server/src/main/scala/server/Main.scala @@ -1,18 +1,10 @@ package server -import akka.actor.{ActorRef, ActorSystem} -import akka.util.Timeout -import server.actors.GameActor -import server.battleship.HumanInterface - -import scala.concurrent.duration._ +import com.twitter.util.Await object Main extends App { - val system = ActorSystem("Battleship-actorsystem") - - implicit val timeout = Timeout(25 millis) - - val gameActor: ActorRef = system.actorOf(GameActor.props(new HumanInterface(HumanInterface.addShips), new HumanInterface(HumanInterface.addShips))) + val defaultPort = 8080 + Await.ready(Server(defaultPort).server) } diff --git a/server/src/main/scala/server/Server.scala b/server/src/main/scala/server/Server.scala index 0558bef..27a00fd 100644 --- a/server/src/main/scala/server/Server.scala +++ b/server/src/main/scala/server/Server.scala @@ -1,10 +1,19 @@ package server +import akka.actor.{ActorRef, ActorSystem} +import akka.pattern.ask +import akka.util.Timeout import com.twitter.finagle.Http import com.twitter.util._ import com.typesafe.scalalogging.LazyLogging -import io.finch.circe._ import io.finch.{Endpoint, Text, _} +import server.actors.GameActor +import server.actors.GameActor.StartGame +import server.battleship.RandomAttackAI +import util.TwitterConverters._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ object Server { @@ -13,9 +22,20 @@ object Server { class Server private[server](port: Int) extends LazyLogging { + val system = ActorSystem("Battleship-actorsystem") + + implicit val timeout = Timeout(25 millis) + val api: Endpoint[String] = get("hello") { Ok("Hello, world!") } - val server = Http.server.serve(s":$port", api.toServiceAs[Text.Plain]) + val start: Endpoint[String] = post("start") { + val gameActor: ActorRef = system.actorOf(GameActor.props(RandomAttackAI, RandomAttackAI)) + scalaToTwitterFuture(gameActor ? StartGame map(_ => Ok("started"))) + } + + val endpoints = api :+: start + + val server = Http.server.serve(s":$port", endpoints.toServiceAs[Text.Plain]) logger.debug(s"Starting server at $port") def terminate: Future[Unit] = { diff --git a/server/src/main/scala/server/actors/GameActor.scala b/server/src/main/scala/server/actors/GameActor.scala index bc327f7..66d1c7e 100644 --- a/server/src/main/scala/server/actors/GameActor.scala +++ b/server/src/main/scala/server/actors/GameActor.scala @@ -2,6 +2,7 @@ package server.actors import akka.actor.{Actor, ActorRef, Props} import akka.util.Timeout +import server.actors.GameActor.{Done, StartGame} import server.actors.Protocol.{Attack, GetAttack, ProcessAttackResult} import server.battleship.{GameState, Player, Win} @@ -9,6 +10,10 @@ import scala.concurrent.duration._ object GameActor { + case object StartGame + + case object Done + def props(player1: Player, player2: Player): Props = Props(new GameActor(player1, player2)) } @@ -23,19 +28,22 @@ class GameActor(player1: Player, player2: Player) extends Actor { val initialGame = GameState.create(player1, player2) - override def preStart(): Unit = players(initialGame.playerOnTurn) ! GetAttack - - override def receive: Receive = ongoingGame(initialGame) + override def receive: Receive = { + case StartGame => + players(initialGame.playerOnTurn) ! GetAttack + context.become(ongoingGame(initialGame, context.sender())) + } - def ongoingGame(game: GameState): Receive = { + def ongoingGame(game: GameState, parent: ActorRef): Receive = { case Attack(row, col) => val (newGame, attackResult) = game.processMove(row, col) players(game.playerOnTurn) ! ProcessAttackResult(attackResult) println(newGame.gameStateAsString) if (attackResult != Win) { - context.become(ongoingGame(newGame)) + context.become(ongoingGame(newGame, parent)) players(newGame.playerOnTurn) ! GetAttack } else { + parent ! Done } case otherMsg => println(s"Not handling $otherMsg") diff --git a/server/src/main/scala/util/TwitterConverters.scala b/server/src/main/scala/util/TwitterConverters.scala new file mode 100644 index 0000000..0077ccb --- /dev/null +++ b/server/src/main/scala/util/TwitterConverters.scala @@ -0,0 +1,31 @@ +package util + +import com.twitter.{util => twitter} + +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.language.implicitConversions +import scala.util.{Failure, Success, Try} + +object TwitterConverters { + implicit def scalaToTwitterTry[T](t: Try[T]): twitter.Try[T] = t match { + case Success(r) => twitter.Return(r) + case Failure(ex) => twitter.Throw(ex) + } + + implicit def twitterToScalaTry[T](t: twitter.Try[T]): Try[T] = t match { + case twitter.Return(r) => Success(r) + case twitter.Throw(ex) => Failure(ex) + } + + def scalaToTwitterFuture[T](f: Future[T])(implicit ec: ExecutionContext): twitter.Future[T] = { + val promise = twitter.Promise[T]() + f.onComplete(promise update _) + promise + } + + def twitterToScalaFuture[T](f: twitter.Future[T]): Future[T] = { + val promise = Promise[T]() + f.respond(promise complete _) + promise.future + } +}