Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add start endpoint to run concurrent games #19

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion server/src/it/scala/server/IntegrationTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = _

Expand All @@ -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] = {
Expand All @@ -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)
}
Expand Down
14 changes: 3 additions & 11 deletions server/src/main/scala/server/Main.scala
Original file line number Diff line number Diff line change
@@ -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)
}
24 changes: 22 additions & 2 deletions server/src/main/scala/server/Server.scala
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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] = {
Expand Down
18 changes: 13 additions & 5 deletions server/src/main/scala/server/actors/GameActor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ 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}

import scala.concurrent.duration._

object GameActor {

case object StartGame

case object Done

def props(player1: Player, player2: Player): Props = Props(new GameActor(player1, player2))
}

Expand All @@ -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")
Expand Down
31 changes: 31 additions & 0 deletions server/src/main/scala/util/TwitterConverters.scala
Original file line number Diff line number Diff line change
@@ -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
}
}