implement simulator with player bots

This commit is contained in:
Thibault Duplessis 2013-10-30 00:37:00 +01:00
parent 0f9830d447
commit 5011153534
6 changed files with 286 additions and 2 deletions

View file

@ -56,7 +56,6 @@ final class Env(
Env.tournament,
Env.lobby,
Env.game,
Env.ai,
Env.setup,
Env.round,
Env.team,
@ -74,6 +73,10 @@ final class Env(
}
if (Env.ai.isServer) println("Running as AI server")
if (config getBoolean "simulation.enabled") {
lila.simulation.Env.current.start
}
}
object Env {

View file

@ -0,0 +1,190 @@
package lila.simulation
import scala.concurrent.duration._
import akka.actor._
import akka.pattern.{ ask, pipe }
import chess.Color
import ornicar.scalalib.Random
import play.api.libs.iteratee._
import play.api.libs.iteratee.Concurrent.Channel
import play.api.libs.json._
import actorApi._
import lila.common.PimpedJson._
import lila.user.User
private[simulation] final class Bot(
name: String,
lobbyEnv: lila.lobby.Env,
roundEnv: lila.round.Env) extends Actor with FSM[Bot.State, Bot.Data] {
import Bot._
val uid = Random nextString 8
val user = none[User]
val sid = none[String]
val ip = "127.0.0.1"
log("spawned")
startWith(Offline, NoData)
when(Offline) {
case Event(Start, _) goto(LobbyConnect)
}
onTransition {
case _ -> LobbyConnect lobbyEnv.socketHandler(uid, user) pipeTo self
}
when(LobbyConnect) {
case Event((iteratee: JsIteratee, enumerator: JsEnumerator), _) {
receiveFrom(enumerator)
val hook = lila.setup.HookConfig.default.hook(uid, user, sid)
lobbyEnv.lobby ! lila.lobby.actorApi.AddHook(hook)
goto(Lobby) using Lobbyist(sendTo(iteratee))
}
}
when(Lobby) {
case Event(Message("redirect", obj), Lobbyist(channel)) obj str "d" map { url
channel.eofAndEnd()
val id = url drop 1
roundEnv.socketHandler.player(id, 0, uid, "token", user, ip) pipeTo self
goto(RoundConnect) using FullId(id)
} getOrElse stay
}
when(RoundConnect) {
case Event((iteratee: JsIteratee, enumerator: JsEnumerator), FullId(id)) {
log(s"joins ${id}")
receiveFrom(enumerator)
delay(0.5 second)(self ! Ping)
delay(1 second)(self ! Move)
goto(Round) using Player(id, sendTo(iteratee))
}
}
when(Round) {
// pong
case Event(Message("n", obj), _) {
delay(1 second)(self ! Ping)
stay
}
// batch
case Event(Message("b", obj), _) {
val events = ~(obj.arrAs("d")(_.asOpt[JsObject]))
events.map(parseMessage).flatten foreach self.!
stay
}
case Event(Message("end", _), player: Player) {
player.channel.eofAndEnd()
goto(LobbyConnect) using NoData
}
// any other versioned event
case Event(Message(_, obj), _) {
log(obj.toString)
setVersion(obj)
stay
}
case Event(Move, player: Player) {
val (from, to) = moves(player.move % moves.size)
val move = Json.obj(
"t" -> "move",
"d" -> Json.obj(
"from" -> from.key,
"to" -> to.key
)
)
player.channel push move
delay(1 second)(self ! Move)
stay using player.copy(move = player.move + 1)
}
case Event(Ping, player: Player) {
player.channel push Json.obj("t" -> "p", "v" -> player.v)
stay
}
case Event(SetVersion(v), player: Player)
stay using player.copy(v = v)
}
whenUnhandled {
case _ stay
}
def setVersion(obj: JsObject) {
obj int "v" map SetVersion.apply foreach self.!
}
def receiveFrom(enumerator: Enumerator[JsValue]) {
enumerator &> parsingMessage |>> Iteratee.foreach(self.!)
}
def sendTo(iteratee: Iteratee[JsValue, _]): JsChannel = {
val (enumerator, channel) = Concurrent.broadcast[JsValue]
enumerator |>> iteratee
channel
}
def log(msg: String) {
println(s"${name} ${msg}")
}
def delay(duration: FiniteDuration)(action: Unit) {
context.system.scheduler.scheduleOnce(duration)(action)
}
}
private[simulation] object Bot {
sealed trait State
case object Offline extends State
case object LobbyConnect extends State
case object Lobby extends State
case object RoundConnect extends State
case object Round extends State
sealed trait Data
case object NoData extends Data
case class Lobbyist(channel: JsChannel) extends Data
case class FullId(id: String) extends Data
case class Player(
id: String,
channel: JsChannel,
v: Int = 1,
move: Int = 0) extends Data
case object Ping
case object Move
case class SetVersion(v: Int)
// type, full object
case class Message(t: String, obj: JsObject)
val parsingMessage: Enumeratee[JsValue, Message] =
Enumeratee.mapInput[JsValue] {
case Input.El(js) parseMessage(js).fold[Input[Message]](Input.Empty)(Input.El.apply)
case _ Input.Empty
}
def parseMessage(js: JsValue): Option[Message] =
js.asOpt[JsObject] flatMap { obj
obj str "t" map { Message(_, obj) }
}
import chess.Pos._
val moves = IndexedSeq(E2 -> E4, D7 -> D5, E4 -> D5, D8 -> D5, B1 -> C3, D5 -> A5, D2 -> D4, C7 -> C6, G1 -> F3, C8 -> G4, C1 -> F4, E7 -> E6, H2 -> H3, G4 -> F3, D1 -> F3, F8 -> B4, F1 -> E2, B8 -> D7, A2 -> A3, E8 -> C8, A3 -> B4, A5 -> A1, E1 -> D2, A1 -> H1, F3 -> C6, B7 -> C6, E2 -> A6)
}

View file

@ -0,0 +1,31 @@
package lila.simulation
import scala.concurrent.duration._
import akka.actor._
import com.typesafe.config.Config
final class Env(
config: Config,
lobbyEnv: lila.lobby.Env,
roundEnv: lila.round.Env,
system: ActorSystem) {
private lazy val simulator = system.actorOf(Props(new Simulator(
lobbyEnv = lobbyEnv,
roundEnv = roundEnv
)), name = "simulator")
def start {
system.scheduler.scheduleOnce(2 seconds, simulator, actorApi.Start)
}
}
object Env {
lazy val current = "[boot] simulation" describes new Env(
config = lila.common.PlayApp loadConfig "simulation",
lobbyEnv = lila.lobby.Env.current,
roundEnv = lila.round.Env.current,
system = lila.common.PlayApp.system)
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,15 @@
package lila
import lila.socket.WithSocket
package object simulation
extends PackageObject
with WithPlay
with WithSocket {
private[simulation] object actorApi {
case object Start
case object Spawn
}
}

View file

@ -29,7 +29,7 @@ object ApplicationBuild extends Build {
message, notification, i18n, game, bookmark, search,
gameSearch, timeline, forum, forumSearch, team, teamSearch,
ai, analyse, mod, monitor, site, round, lobby, setup,
importer, tournament, relation, report, pref)
importer, tournament, relation, report, pref, simulation)
lazy val moduleRefs = modules map projectToRef
lazy val moduleCPDeps = moduleRefs map { new sbt.ClasspathDependency(_, None) }
@ -41,6 +41,11 @@ object ApplicationBuild extends Build {
actuarius, scalastic, findbugs, reactivemongo)
) aggregate (moduleRefs: _*)
lazy val simulation = project("simulation", Seq(
common, hub, socket, game, round, setup)).settings(
libraryDependencies ++= provided(play.api, reactivemongo)
)
lazy val common = project("common").settings(
libraryDependencies ++= provided(play.api, play.test, reactivemongo, csv)
)