Skip to content
Permalink
Browse files
Batch API
  • Loading branch information
cswinter committed Mar 17, 2019
1 parent 5cf2781 commit b8a94f9
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 5 deletions.
@@ -43,6 +43,23 @@ class Application @Inject()(
Ok(write(payload)).as("application/json")
}

def batchAct() = Action { implicit request =>
println("batchAct")
val actions = read[Map[String, Action]](request.body.asJson.get.toString)
for ((gameID, action) <- actions) {
multiplayerServer.act(gameID.toInt, 0, action)
}
Ok("success").as("application/json")
}

def batchPlayerState() = Action { implicit request =>
println("batchObserve")
val games = read[Seq[Int]](request.body.asJson.get.toString)
val payload: Seq[Observation] = for (gameID <- games)
yield multiplayerServer.observe(gameID, 0)
Ok(write(payload)).as("application/json")
}

def mpssJson = Action.async { implicit request =>
val maxGameStats = request.getQueryString("maxgames").fold(250)(x => Try { x.toInt }.getOrElse(250))
implicit val timeout = Timeout(1 seconds)
@@ -53,4 +70,8 @@ class Application @Inject()(
lessGames = status.games.sortBy(-_.startTimestamp).take(maxGameStats)
} yield Ok(write(status.copy(games = lessGames))).as("application/json")
}

def debugState = Action {
Ok(write(multiplayerServer.debugState)).as("application/json")
}
}
@@ -11,8 +11,7 @@ import play.api.inject.ApplicationLifecycle
import scala.concurrent.duration._
import scala.language.postfixOps

import scala.concurrent.Await
import scala.concurrent.{Promise, Future}
import scala.concurrent.{Await, Promise, Future, TimeoutException}

@Singleton
class MultiplayerServer @Inject()(lifecycle: ApplicationLifecycle) {
@@ -35,20 +34,36 @@ class MultiplayerServer @Inject()(lifecycle: ApplicationLifecycle) {


def observe(gameID: Int, playerID: Int): Observation = synchronized {
println(f"Enter Observe $gameID")
if (completedGames.contains(gameID)) return completedGames(gameID)
val game = games(gameID)
val observation = game.externalPlayers(playerID).observe(game.simulator)
if (!observation.winner.isEmpty) {
games -= gameID
completedGames += gameID -> observation
}
println(f"Exit Observe $gameID")
observation
}

def act(gameID: Int, playerID: Int, action: Action): Unit = synchronized {
println(f"Enter Act $gameID")
val game = games(gameID)
if (!game.simulator.winner.isEmpty) return
game.externalPlayers(playerID).act(action)
println(f"Exit Act $gameID")
}

def debugState(): Seq[GameDebugState] = {
for ((id, game) <- games.toSeq) yield {
GameDebugState(
id,
game.externalPlayers(0).observationsReady.isCompleted,
game.simulator.winner.map(_.id),
game.simulator.currentPhase.toString,
game.externalPlayers(0).unsafe_observe(game.simulator)
)
}
}

// it appears the class loader will not work during shutdown, so we need to get an instance of Server.Stop
@@ -70,7 +85,14 @@ class PassiveDroneController(
val closest = enemiesInSight.minBy(enemy => (enemy.position - position).lengthSquared)
if (isInMissileRange(closest)) fireMissilesAt(closest)
}
val action = Await.result(nextAction.future, Duration.Inf)
val action = try {
Await.result(nextAction.future,Duration.Inf)// 60 seconds)
} catch {
case e: TimeoutException => {
println("DROPPED ACTION")
DoNothing
}
}

for (spec <- action.buildDrone) {
buildDrone(new PassiveDroneController(state), spec(0), spec(1), spec(2), spec(3), spec(4))
@@ -118,7 +140,7 @@ class PassiveDroneController(

class PlayerController(
var alliedDrones: Set[PassiveDroneController] = Set.empty,
var observationsReady: Promise[Unit] = Promise()
@volatile var observationsReady: Promise[Unit] = Promise()
) extends MetaController {
def observe(sim: DroneWorldSimulator): Observation = {
Await.ready(observationsReady.future, Duration.Inf)
@@ -131,6 +153,15 @@ class PlayerController(
)
}

def unsafe_observe(sim: DroneWorldSimulator): Observation = {
Observation(
sim.timestep,
sim.winner.map(_.id),
for (d <- alliedDrones.toSeq)
yield DroneObservation(d.position.x, d.position.y, d.orientation.toFloat)
)
}

def act(action: Action): Unit = {
observationsReady = Promise()
for (d <- alliedDrones) d.setAction(action)
@@ -141,7 +172,9 @@ class PlayerController(
}

override def gameOver(winner: Player): Unit = {
observationsReady.success(())
if (!observationsReady.isCompleted) {
observationsReady.success(())
}
}
}

@@ -172,3 +205,12 @@ object DoNothing extends Action(
transfer=false,
turn=0
)

case class GameDebugState(
id: Int,
observationsReady: Boolean,
winner: Option[Int],
phase: String,
observation: Observation
)

@@ -8,8 +8,11 @@ GET /observe com.clemenswinter.codecraftse
POST /start-game com.clemenswinter.codecraftserver.controllers.Application.startGame
GET /observation com.clemenswinter.codecraftserver.controllers.Application.playerState(gameID: Int, playerID: Int)
POST /act com.clemenswinter.codecraftserver.controllers.Application.act(gameID: Int, playerID: Int)
GET /batch-observation com.clemenswinter.codecraftserver.controllers.Application.batchPlayerState()
POST /batch-act com.clemenswinter.codecraftserver.controllers.Application.batchAct()

GET /ajax/multiplayerServerStatus com.clemenswinter.codecraftserver.controllers.Application.mpssJson
GET /debugState com.clemenswinter.codecraftserver.controllers.Application.debugState

# Prefix must match `play.assets.urlPrefix`
GET /assets/*file controllers.Assets.at(file)

0 comments on commit b8a94f9

Please sign in to comment.