Skip to content

Commit 9b9c45d

Browse files
committed
Additional refactoring towards pluggable games
- package `scalatron.scalatron` now makes only a single reference to `scalatron.botwar`, which will be replaced by the plug-in loading mechanism
1 parent 8a16de7 commit 9b9c45d

13 files changed

+145
-84
lines changed

Scalatron/src/scalatron/botwar/Board.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
*/
44
package scalatron.botwar
55

6-
import State.Time
6+
import scalatron.core.{Simulation, Plugin}
7+
import Simulation.Time
78
import scala.util.Random
89
import BoardParams.Perimeter
910
import akka.dispatch.{Await, Future, ExecutionContext}
1011
import akka.util.duration._
1112
import java.util.concurrent.TimeoutException
12-
import scalatron.core.Plugin
1313

1414

1515
/** Contains the temporally variable aspects of the game state.

Scalatron/src/scalatron/botwar/BotWar.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,12 @@ case object BotWar extends Game
235235
*/
236236
def startHeadless(
237237
plugins: Iterable[Plugin.FromJarFile],
238-
permanentConfig: PermanentConfig,
239-
gameConfig: Config
238+
roundConfig: RoundConfig
240239
)(
241240
executionContextForUntrustedCode: ExecutionContext
242241
) : SimState = {
242+
val gameConfig = Config.create(roundConfig.permanent, roundConfig.roundIndex, plugins, roundConfig.argMap)
243+
243244
// update the game configuration based on the plug-ins that are loaded
244245
val factory = BotWarSimulation.Factory(gameConfig)
245246

Scalatron/src/scalatron/botwar/BotWarSimulation.scala

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44
package scalatron.botwar
55

66
import scala.util.Random
7-
import scalatron.core.{Plugin, TournamentRoundResult}
87
import akka.dispatch.ExecutionContext
98
import akka.actor.ActorSystem
9+
import scalatron.core.{Simulation, Plugin}
1010

1111

1212
/** Implementations of generic Simulation traits for the BotWar game. */
1313
object BotWarSimulation
1414
{
15-
case class SimState(gameState: State) extends Simulation.State[SimState,TournamentRoundResult] {
15+
case class SimState(gameState: State) extends Simulation.State[SimState] {
16+
def time = gameState.time
1617
def step(actorSystem: ActorSystem, executionContextForUntrustedCode: ExecutionContext) = {
1718
// to make results reproducible, generate a freshly seeded randomizer for every cycle
1819
val rnd = new Random(gameState.time)
@@ -23,10 +24,32 @@ object BotWarSimulation
2324
case Right(gameResult) => Right(gameResult)
2425
}
2526
}
27+
/** Returns a collection containing all entities controlled by the control function implemented in the plug-in
28+
* (and thus associated with the player) with the given name. */
29+
def entitiesOfPlayer(name: String) =
30+
gameState.board.entitiesOfPlayer(name).map(e => new Simulation.Entity {
31+
def id = e.id
32+
def name = e.name
33+
def isMaster = e.isMaster
34+
def mostRecentControlFunctionInput = e.variety match {
35+
case player: Bot.Player => player.controlFunctionInput
36+
case _ => ""
37+
}
38+
def mostRecentControlFunctionOutput = e.variety match {
39+
case player: Bot.Player =>
40+
val commands = player.controlFunctionOutput
41+
commands.map(command => (command.opcode, command.paramMap.map(e => (e._1, e._2.toString))))
42+
case _ => Iterable.empty
43+
}
44+
def debugOutput = e.variety match {
45+
case player: Bot.Player => player.stateMap.getOrElse(Protocol.PropertyName.Debug, "")
46+
case _ => ""
47+
}
48+
})
2649
}
2750

2851

29-
case class Factory(config: Config) extends Simulation.Factory[SimState,TournamentRoundResult] {
52+
case class Factory(config: Config) extends Simulation.Factory[SimState] {
3053
def createInitialState(randomSeed: Int, plugins: Iterable[Plugin.FromJarFile])(executionContextForUntrustedCode: ExecutionContext) = {
3154
val state = State.createInitial(config, randomSeed, plugins)(executionContextForUntrustedCode)
3255
SimState(state)

Scalatron/src/scalatron/botwar/Entity.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
*/
44
package scalatron.botwar
55

6-
import State.Time
6+
import scalatron.core.{Simulation, Plugin}
7+
import Simulation.Time
78
import util.Random
89
import java.lang.IllegalStateException
9-
import scalatron.core.Plugin
1010

1111

1212
/** A game entity. An entity can be a collidable Bot (player, slave, beast, plant, wall) or

Scalatron/src/scalatron/botwar/State.scala

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
*/
44
package scalatron.botwar
55

6-
import State.Time
6+
import scalatron.core.{Simulation, Plugin}
7+
import Simulation.Time
78
import akka.dispatch.ExecutionContext
8-
import scalatron.core.Plugin
99

1010

1111
/** Game state storing the current (game) time, the board parameters, the actual board (i.e.
@@ -29,14 +29,6 @@ case class State(
2929

3030
object State
3131
{
32-
type Time = Long
33-
34-
object Time {
35-
val MaxValue = Long.MaxValue
36-
val SomtimeInThePast = -1
37-
}
38-
39-
4032
def createInitial(config: Config, randomSeed: Int, combinedPlugins: Iterable[Plugin] )(executionContextForUntrustedCode: ExecutionContext) = {
4133
val time = 0L
4234
val board =

Scalatron/src/scalatron/core/Game.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,11 @@ trait Game
7070
)
7171

7272

73-
def startHeadless(
74-
plugins: Iterable[Plugin.FromJarFile],
75-
permanentConfig: PermanentConfig,
76-
gameConfig: Config
77-
)(
78-
executionContextForUntrustedCode: ExecutionContext
79-
): SimState
80-
73+
/** Starts a headless, private game instances and returns the associated initial simulation state.
74+
* @param plugins the plug-ins to load into the game
75+
* @param roundConfig the configuration for this game round (includes the permanent configuration by reference)
76+
* @param executionContextForUntrustedCode execution context for untrusted code (e.g. for bot control functions)
77+
* @return an initial simulation state.
78+
*/
79+
def startHeadless(plugins: Iterable[Plugin.FromJarFile], roundConfig: RoundConfig)(executionContextForUntrustedCode: ExecutionContext): SimState
8180
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package scalatron.core
2+
3+
/** Configuration for a specific game round. Incorporates the permanent configuration settings by reference.
4+
* @param permanent the permanent configuration (number of steps per round, secure mode, etc.)
5+
* @param argMap the argument settings for this game round (arena size, etc.)
6+
* @param roundIndex the index of this round within the tournament (zero-based, monotonically increasing)
7+
*/
8+
case class RoundConfig(
9+
permanent: PermanentConfig, // configuration settings that won't change round-to-round
10+
argMap: Map[String,String],
11+
roundIndex: Int
12+
)

Scalatron/src/scalatron/botwar/Simulation.scala renamed to Scalatron/src/scalatron/core/Simulation.scala

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,104 @@
1+
package scalatron.core
2+
13
/** This material is intended as a community resource and is licensed under the
24
* Creative Commons Attribution 3.0 Unported License. Feel free to use, modify and share it.
35
*/
4-
package scalatron.botwar
56

67
import akka.util.Duration
78
import akka.dispatch.{ExecutionContext, Future, Await}
89
import akka.actor.ActorSystem
9-
import scalatron.core.Plugin
1010

1111

1212
/** Traits for generic simulations, of which a game like BotWar is an example.
1313
*/
1414
object Simulation
1515
{
16+
type Time = Long
17+
18+
object Time
19+
{
20+
val MaxValue = Long.MaxValue
21+
val SomtimeInThePast = -1
22+
}
23+
24+
25+
/** Base trait for entities (bots, etc.) exposed by a simulation. */
26+
trait Entity {
27+
/** Returns the unique ID of this entity. */
28+
def id: Int
29+
30+
/** Returns the unique name of this entity, e.g. "Master" or "Slave_12345". */
31+
def name: String
32+
33+
/** Returns true if this entity is a master (bot), false if it is a slave (mini-bot). */
34+
def isMaster: Boolean
35+
36+
/** Returns the input string that was most recently received by the control function of
37+
* this entity. Note that for Master bots this may be out of date, since they are only
38+
* called every second cycle. */
39+
def mostRecentControlFunctionInput: String
40+
41+
/** Returns the list of commands that was most recently returned by the control function
42+
* of this entity. Note that for Master bots this may be out of date, since they are only
43+
* called every second cycle. Also note that this is NOT the command string returned by
44+
* the bot; rather, it is the collection of commands actually recognized and accepted by
45+
* the server. The returned structure is: Iterable[(opcode,Iterable[(paramName,value])]
46+
*/
47+
def mostRecentControlFunctionOutput: Iterable[(String, Iterable[(String, String)])]
48+
49+
/** Returns the debug log output most recently made by the bot. */
50+
def debugOutput: String
51+
}
52+
53+
54+
1655
/** Simulation.UntypedState: non-polymorphic base trait for State that simplifies passing State to contexts
1756
* where we don't want to introduce the types of S and R.
1857
*/
1958
trait UntypedState
59+
{
60+
/** @return the time value associated with this simulation state; a monotonically increasing, zero-based, Long integer step counter. */
61+
def time: Time
62+
63+
/** Advances the simulation one step and returns either an updated state or a result.
64+
* @param actorSystem the actor system to use for trusted computation.
65+
* @param executionContextForUntrustedCode the execution context to use for untrusted computation.
66+
* @return either an updated state or a result.
67+
*/
68+
def step(actorSystem: ActorSystem, executionContextForUntrustedCode: ExecutionContext): Either[UntypedState,TournamentRoundResult]
69+
70+
/** Returns a collection containing all entities controlled by the control function implemented in the plug-in
71+
* (and thus associated with the player) with the given name. */
72+
def entitiesOfPlayer(name: String) : Iterable[Entity]
73+
}
74+
75+
2076

2177
/** Simulation.State: base traits for simulation state implementations.
2278
* @tparam S type of the simulation state implementation (extends Simulation.State)
23-
* @tparam R type of the result returned by the simulator (arbitrary)
2479
*/
25-
trait State[S <: State[S, R], R] extends UntypedState {
26-
def step(actorSystem: ActorSystem, executionContextForUntrustedCode: ExecutionContext) : Either[S, R]
80+
trait State[S <: State[S]] extends UntypedState
81+
{
82+
def step(actorSystem: ActorSystem, executionContextForUntrustedCode: ExecutionContext): Either[S,TournamentRoundResult]
2783
}
2884

2985

3086
/** Simulation.Factory: base traits for simulation state factory implementations.
3187
* @tparam S type of the simulation state implementation (extends Simulation.State)
32-
* @tparam R type of the result returned by the simulator (arbitrary)
3388
*/
34-
trait Factory[S <: State[S, R], R] {
35-
def createInitialState(randomSeed: Int, plugins: Iterable[Plugin.FromJarFile])(executionContextForUntrustedCode: ExecutionContext) : S
89+
trait Factory[S <: State[S]]
90+
{
91+
def createInitialState(randomSeed: Int, plugins: Iterable[Plugin.FromJarFile])(executionContextForUntrustedCode: ExecutionContext): S
3692
}
3793

3894
/** Simulation.Runner: a generic runner for simulations that uses .step() to iteratively
3995
* compute new simulation states.
4096
* @tparam S type of the simulation state implementation (extends Simulation.State)
41-
* @tparam R type of the result returned by the simulator (arbitrary)
4297
*/
43-
case class Runner[S <: State[S, R], R](
44-
factory: Factory[S, R],
98+
case class Runner[S <: State[S]](
99+
factory: Factory[S],
45100
stepCallback: S => Boolean, // callback invoked at end of every simulation step; if it returns false, the sim terminates without result
46-
resultCallback: (S, R) => Unit ) // callback invoked after the end of very last simulation step
101+
resultCallback: (S,TournamentRoundResult) => Unit) // callback invoked after the end of very last simulation step
47102
{
48103
/** @param plugins the collection of external plug-ins to bring into the simulation
49104
* @param randomSeed the random seed to use for initializing the simulation
@@ -57,28 +112,28 @@ object Simulation
57112
)(
58113
actorSystem: ActorSystem,
59114
executionContextForUntrustedCode: ExecutionContext
60-
): Option[R] =
115+
): Option[TournamentRoundResult] =
61116
{
62117
var currentState = factory.createInitialState(randomSeed, plugins)(executionContextForUntrustedCode) // state at entry of loop turn
63-
var priorStateOpt : Option[S] = None // result of state.step() at exit of prior loop turn
64-
var finalResult: Option[R] = None
118+
var priorStateOpt: Option[S] = None // result of state.step() at exit of prior loop turn
119+
var finalResult: Option[TournamentRoundResult] = None
65120

66121
var running = true
67-
while( running ) {
122+
while(running) {
68123
// we'll use Akka Futures to compute the next state concurrently with the callback on the prior state.
69124
// the callback will generally render the prior state to the screen.
70125

71126
// process state update, returns either next state or result
72127
val executionContextForTrustedCode = actorSystem.dispatcher
73-
val stepFuture = Future( { currentState.step(actorSystem, executionContextForUntrustedCode) } )(executionContextForTrustedCode) // compute next state
128+
val stepFuture = Future({currentState.step(actorSystem, executionContextForUntrustedCode)})(executionContextForTrustedCode) // compute next state
74129

75130
// process callback (usually rendering) on prior state, returns true if to continue simulating, false if not
76-
val callbackFuture = Future( {
131+
val callbackFuture = Future({
77132
priorStateOpt match {
78133
case None => true // there is no state to call back about (e.g. nothing to render)
79134
case Some(priorState) => stepCallback(priorState)
80135
}
81-
} )(executionContextForTrustedCode)
136+
})(executionContextForTrustedCode)
82137

83138
// let the processing complete
84139
val stepResult = Await.result(stepFuture, Duration.Inf)

Scalatron/src/scalatron/core/TournamentState.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package scalatron.core
44
* Creative Commons Attribution 3.0 Unported License. Feel free to use, modify and share it.
55
*/
66

7-
import scalatron.botwar.Simulation
87
import TournamentState.LeaderBoard
98
import scalatron.core.TournamentRoundResult.AggregateResult
109

Scalatron/src/scalatron/scalatron/impl/ScalatronSandbox.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ package scalatron.scalatron.impl
55
*/
66

77

8-
import scalatron.botwar.BotWarSimulation
98
import scalatron.scalatron.api.Scalatron
9+
import scalatron.core.Simulation
1010

1111

12-
case class ScalatronSandbox(id: Int, user: ScalatronUser, initialSimState: BotWarSimulation.SimState) extends Scalatron.Sandbox {
12+
case class ScalatronSandbox(id: Int, user: ScalatronUser, initialSimState: Simulation.UntypedState) extends Scalatron.Sandbox {
1313
def initialState = ScalatronSandboxState(this, initialSimState)
1414
}

0 commit comments

Comments
 (0)