Skip to content

Commit 0bc4641

Browse files
committed
More refactoring ahead of split into multiple modules
1 parent 9b9c45d commit 0bc4641

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+587
-680
lines changed

Scalatron/src/scalatron/botwar/AugmentedDynamics.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ case object AugmentedDynamics extends ((State,Random,Iterable[(Entity.Id,Iterabl
2020
// inform the plug-ins that the game is over
2121
masterBots.foreach(bot => try {
2222
bot.variety match {
23-
case player: Bot.Player => player.controlFunction(Protocol.ServerOpcode.Goodbye + "(energy=" + bot.energy + ")")
23+
case player: Bot.Player =>
24+
// TODO: isolate this into the untrusted execution context
25+
player.entityController.respond(Protocol.ServerOpcode.Goodbye + "(energy=" + bot.energy + ")")
2426
case _ => throw new IllegalStateException("expected master bot")
2527
}
2628
} catch {
@@ -240,7 +242,7 @@ case object AugmentedDynamics extends ((State,Random,Iterable[(Entity.Id,Iterabl
240242
.updated(Protocol.PropertyName.Debug, logMessage + "\n" + disable.text)
241243
val updatedVariety =
242244
thisPlayer.copy(
243-
controlFunction = (in: String) => "Log(text=" + logMessage + ")", // make sure it's never called again (but note that sibling minibots/bots use their own ref)
245+
entityController = thisPlayer.entityController.withReplacedControlFunction( (in: String) => "Log(text=" + logMessage + ")"), // make sure it's never called again (but note that sibling minibots/bots use their own ref)
244246
stateMap = updatedStateMap)
245247
val updatedThisBot = thisBot.updateVariety(updatedVariety)
246248
updatedBoard = updatedBoard.updateBot(updatedThisBot)
@@ -397,7 +399,7 @@ case object AugmentedDynamics extends ((State,Random,Iterable[(Entity.Id,Iterabl
397399
bonk()
398400
} else {
399401
// master on slave
400-
if(movingPlayer.plugin == steppedOnPlayer.plugin) {
402+
if(movingPlayer.entityController == steppedOnPlayer.entityController) {
401403
// master on own slave -- re-absorb
402404
updatedBoard = updatedBoard.removeBot(steppedOnBot.id)
403405
val energyDelta = steppedOnBot.energy
@@ -415,7 +417,7 @@ case object AugmentedDynamics extends ((State,Random,Iterable[(Entity.Id,Iterabl
415417
} else {
416418
if(steppedOnPlayer.isMaster) {
417419
// slave on master -- always disappears
418-
if(movingPlayer.plugin == steppedOnPlayer.plugin) {
420+
if(movingPlayer.entityController == steppedOnPlayer.entityController) {
419421
// slave on own master -- gets re-absorbed
420422
updatedBoard = updatedBoard.removeBot(movingBot.id)
421423
val energyDelta = movingBot.energy

Scalatron/src/scalatron/botwar/Board.scala

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

6-
import scalatron.core.{Simulation, Plugin}
6+
import scalatron.core.{Simulation, EntityController}
77
import Simulation.Time
88
import scala.util.Random
99
import BoardParams.Perimeter
@@ -88,10 +88,10 @@ case class Board(
8888
def addBot(pos: XY, extent: XY, creationTime: Time, energy: Int, variety: Bot.Variety) : Board =
8989
copy(nextId = nextId + 1, bots = bots.updated(nextId, Bot(nextId, pos, extent, creationTime, Time.SomtimeInThePast, energy, variety)))
9090

91-
def addBotThatIsMaster(pos: XY, creationTime: Time, controlFunction: (String => String), plugin: Plugin) : Board =
91+
def addBotThatIsMaster(pos: XY, creationTime: Time, entityController: EntityController) : Board =
9292
copy(nextId = nextId + 1, bots = bots.updated(nextId,
9393
Bot(nextId, pos, XY.One, creationTime, Time.SomtimeInThePast, Constants.Energy.Initial,
94-
Bot.Player(controlFunction, plugin, Bot.MasterGeneration, nextId, (0,2), 0L, "", Iterable.empty, Map(Protocol.PropertyName.Name -> plugin.name))
94+
Bot.Player(entityController, Bot.MasterGeneration, nextId, (0,2), 0L, "", Iterable.empty, Map(Protocol.PropertyName.Name -> entityController.name))
9595
)))
9696

9797
def sprinkle(count: Int, rnd: Random, creationTime: Time, boardSize: XY, variety: Bot.Variety) =
@@ -156,7 +156,8 @@ object Board
156156
time: Time,
157157
stepsPerRound: Int,
158158
roundIndex: Int,
159-
randomSeed: Int, combinedPlugins: Iterable[Plugin]
159+
randomSeed: Int,
160+
entityControllers: Iterable[EntityController]
160161
)(
161162
implicit executionContextForUntrustedCode: ExecutionContext
162163
)
@@ -169,47 +170,37 @@ object Board
169170
updatedBoard = spawnPerimeterWalls(boardParams.perimeter, updatedBoard, time, boardSize)
170171
updatedBoard = spawnRandomWalls(rnd, updatedBoard, time, boardSize, boardParams.wallCount)
171172

172-
173-
174-
// generate players' control functions -- use Akka to work this out
173+
// initialize the entity controllers' control functions -- use Akka to work this out
175174
// isolate the control function factory invocation via the untrusted thread pool
176-
val future = Future.traverse(combinedPlugins)(plugin => Future {
177-
// initialize plugins
175+
val future = Future.traverse(entityControllers)(entityController => Future {
178176
try {
179-
val controlFunction = plugin.controlFunctionFactory.apply()
180-
181177
// invoke Welcome()
182-
controlFunction(
178+
entityController.respond(
183179
Protocol.ServerOpcode.Welcome + "(" +
184-
"name=" + plugin.name + "," +
180+
"name=" + entityController.name + "," +
185181
"apocalypse=" + stepsPerRound + "," +
186182
"round=" + roundIndex +
187183
")")
188-
189-
Some((plugin, controlFunction))
190184
} catch {
191185
case t: Throwable =>
192-
System.err.println("error: exception while instantiating control function of plugin '" + plugin.name + "': " + t)
186+
System.err.println("error: exception while instantiating control function of plugin '" + entityController.name + "': " + t)
193187
None
194188
}
195189
})
196190

197191
// Note: an overall timeout across all bots is a temporary solution - we want timeouts PER BOT
198-
val pluginsAndControlFunctions =
199-
try {
200-
val result = Await.result(future, 2000 millis) // generous timeout - note that this is over ALL plug-ins
201-
result.flatten // remove failed instantiations
202-
} catch {
203-
case t: TimeoutException =>
204-
System.err.println("warning: timeout while instantiating control function of one of the plugins")
205-
Iterable.empty // temporary - disables ALL bots, which is not the intention
206-
}
207-
192+
try {
193+
Await.result(future, 2000 millis) // generous timeout - note that this is over ALL plug-ins
194+
} catch {
195+
case t: TimeoutException =>
196+
System.err.println("warning: timeout while instantiating control function of one of the plugins")
197+
Iterable.empty // temporary - disables ALL bots, which is not the intention
198+
}
208199

209200
// spawn players
210-
pluginsAndControlFunctions.foreach( pluginAndControlFunction => {
201+
entityControllers.foreach( entityController => {
211202
val position = updatedBoard.emptyRandomPos(rnd, boardSize)
212-
updatedBoard = updatedBoard.addBotThatIsMaster(position, time, pluginAndControlFunction._2, pluginAndControlFunction._1)
203+
updatedBoard = updatedBoard.addBotThatIsMaster(position, time, entityController)
213204
} )
214205

215206
updatedBoard

Scalatron/src/scalatron/botwar/BotWar.scala

Lines changed: 32 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,27 @@ import renderer.Renderer
77
import scalatron.botwar.BotWarSimulation.SimState
88
import java.awt.event.{WindowEvent, WindowAdapter, KeyEvent, KeyListener}
99
import akka.dispatch.ExecutionContext
10-
import akka.actor.ActorSystem
1110
import scalatron.core._
1211

1312

1413
/** BotWar: an implementation of the Scalatron Game trait.
1514
* Main.main() feeds this instance to Scalatron.run(). */
1615
case object BotWar extends Game
1716
{
18-
val name = Constants.GameName
19-
2017
def gameSpecificPackagePath = "scalatron.botwar.botPlugin"
2118

22-
def runVisually(
23-
pluginPath: String,
24-
argMap: Map[String,String],
25-
rounds: Int,
26-
tournamentState: TournamentState, // receives tournament round results
27-
secureMode: Boolean,
28-
verbose: Boolean
29-
)(
30-
actorSystem: ActorSystem,
31-
executionContextForUntrustedCode: ExecutionContext
32-
)
33-
{
19+
def runVisually(rounds: Int, scalatron: ScalatronInward) {
20+
val argMap = scalatron.argMap
21+
3422
// determine the permanent configuration for the game
35-
val permanentConfig = PermanentConfig.fromArgMap(secureMode, argMap)
23+
val permanentConfig = PermanentConfig.fromArgMap(scalatron.secureMode, argMap)
3624

3725
// pop up the display window, taking command line args into account
3826
val display = Display.create(argMap)
3927
display.show()
4028

4129
// create a renderer; we'll use it to paint the display
42-
val renderer = Renderer(permanentConfig, tournamentState)
30+
val renderer = Renderer(permanentConfig, scalatron)
4331

4432
// add a keyboard listener to allow the user to configure the app while it runs
4533
val keyListener = new KeyListener {
@@ -67,9 +55,9 @@ case object BotWar extends Game
6755

6856
// the simulation runner will invoke this callback, which we use to update the display
6957
val stepCallback = (state: SimState) => {
70-
tournamentState.updateMostRecentState(state)
58+
scalatron.postStepCallback(state)
7159

72-
val executionContextForTrustedCode = actorSystem.dispatcher
60+
val executionContextForTrustedCode = scalatron.actorSystem.dispatcher
7361
renderer.draw(display.renderTarget, state.gameState)(executionContextForTrustedCode)
7462

7563
// enforce maxFPS (max frames per second) by putting this thread to sleep if appropriate
@@ -82,24 +70,15 @@ case object BotWar extends Game
8270
!appShouldExit && !renderer.interactivelyAdjustableSettings.abortThisRound
8371
}
8472

85-
// the simulation runner will invoke this callback, which we use to update the display
86-
val resultCallback = (state: SimState, tournamentRoundResult: TournamentRoundResult) => {
87-
tournamentState.updateMostRecentState(state)
88-
tournamentState.addResult(tournamentRoundResult)
89-
}
90-
91-
92-
var pluginCollection = PluginCollection(pluginPath, gameSpecificPackagePath, verbose)
9373

9474
// now perform game runs ad infinitum
9575
var roundIndex = 0
9676
while(!appShouldExit && roundIndex < rounds) {
9777
// load plugins, either from scratch or incrementally (changed plug-ins only)
98-
pluginCollection = pluginCollection.incrementalRescan // or: fullRescan
99-
val plugins = pluginCollection.plugins
78+
val entityControllers = scalatron.freshEntityControllers
10079

10180
// update the game configuration based on the plug-ins that are loaded
102-
val gameConfig = Config.create(permanentConfig, roundIndex, plugins, argMap)
81+
val gameConfig = Config.create(permanentConfig, roundIndex, entityControllers, argMap)
10382
val factory = BotWarSimulation.Factory(gameConfig)
10483

10584
// prepare a random seed for this game round. Options:
@@ -108,8 +87,8 @@ case object BotWar extends Game
10887
val randomSeed = System.currentTimeMillis.intValue // or: round
10988

11089
// run game
111-
val runner = Simulation.Runner(factory, stepCallback, resultCallback)
112-
runner(plugins, randomSeed)(actorSystem, executionContextForUntrustedCode)
90+
val runner = Simulation.Runner(factory, stepCallback, scalatron.postRoundCallback)
91+
runner(entityControllers, randomSeed)(scalatron.actorSystem, scalatron.executionContextForUntrustedCode)
11392

11493
roundIndex += 1
11594

@@ -121,19 +100,14 @@ case object BotWar extends Game
121100

122101

123102
def runHeadless(
124-
pluginPath: String,
125-
argMap: Map[String,String],
126103
rounds: Int,
127-
tournamentState: TournamentState, // receives tournament round results
128-
secureMode: Boolean,
129-
verbose: Boolean
130-
)(
131-
actorSystem: ActorSystem,
132-
executionContextForUntrustedCode: ExecutionContext
104+
scalatron: ScalatronInward
133105
)
134106
{
107+
val argMap = scalatron.argMap
108+
135109
// determine the permanent configuration for the game
136-
val permanentConfig = PermanentConfig.fromArgMap(secureMode, argMap)
110+
val permanentConfig = PermanentConfig.fromArgMap(scalatron.secureMode, argMap)
137111

138112
// determine the maximum frames/second (to throttle CPU usage or sim loop; min: 1 fps)
139113
val maxFPS = argMap.get("-maxfps").map(_.toInt).getOrElse(50).max(1)
@@ -142,7 +116,7 @@ case object BotWar extends Game
142116

143117
// the simulation runner will invoke this callback, which we use to update the display
144118
val stepCallback = (state: SimState) => {
145-
tournamentState.updateMostRecentState(state)
119+
scalatron.postStepCallback(state)
146120

147121
// enforce maxFPS (max frames per second) by putting this thread to sleep if appropriate
148122
val currentTime = System.currentTimeMillis
@@ -154,25 +128,16 @@ case object BotWar extends Game
154128
true
155129
}
156130

157-
// the simulation runner will invoke this callback, which we use to update the tournament state
158-
val resultCallback = (state: SimState, tournamentRoundResult: TournamentRoundResult) => {
159-
tournamentState.updateMostRecentState(state)
160-
tournamentState.addResult(tournamentRoundResult)
161-
}
162-
163-
var pluginCollection = PluginCollection(pluginPath, gameSpecificPackagePath, verbose)
164-
165131
// now perform game runs ad infinitum
166132
var roundIndex = 0
167133
while(roundIndex < rounds) {
168134
val startTime = System.currentTimeMillis()
169135

170136
// load plugins, either from scratch or incrementally (changed plug-ins only)
171-
pluginCollection = pluginCollection.incrementalRescan // or: fullRescan
172-
val plugins = pluginCollection.plugins
137+
val entityControllers = scalatron.freshEntityControllers
173138

174139
// update the game configuration based on the plug-ins that are loaded
175-
val gameConfig = Config.create(permanentConfig, roundIndex, plugins, argMap)
140+
val gameConfig = Config.create(permanentConfig, roundIndex, entityControllers, argMap)
176141
val factory = BotWarSimulation.Factory(gameConfig)
177142

178143
// prepare a random seed for this game round. Options:
@@ -181,8 +146,8 @@ case object BotWar extends Game
181146
val randomSeed = System.currentTimeMillis.intValue // or: round
182147

183148
// run game
184-
val runner = Simulation.Runner(factory, stepCallback, resultCallback)
185-
runner(plugins, randomSeed)(actorSystem, executionContextForUntrustedCode)
149+
val runner = Simulation.Runner(factory, stepCallback, scalatron.postRoundCallback)
150+
runner(entityControllers, randomSeed)(scalatron.actorSystem, scalatron.executionContextForUntrustedCode)
186151

187152
roundIndex += 1
188153

@@ -196,13 +161,13 @@ case object BotWar extends Game
196161

197162

198163
/** Starts a headless game simulation using the given plug-in collection.
199-
* @param plugins the collection of plug-ins to use as control function factories.
164+
* @param entityControllers the collection of plug-ins to use as control function factories.
200165
* @param secureMode if true, certain bot processing restrictions apply
201166
* @param argMap the command line arguments
202167
* @return the initial simulation state
203168
*/
204169
def startHeadless(
205-
plugins: Iterable[Plugin.FromJarFile],
170+
entityControllers: Iterable[EntityController],
206171
secureMode: Boolean,
207172
argMap: Map[String,String]
208173
)(
@@ -214,7 +179,7 @@ case object BotWar extends Game
214179
val roundIndex = 0
215180

216181
// determine the per-round configuration for the game
217-
val gameConfig : Config = Config.create(permanentConfig, roundIndex, plugins, argMap)
182+
val gameConfig : Config = Config.create(permanentConfig, roundIndex, entityControllers, argMap)
218183

219184
// update the game configuration based on the plug-ins that are loaded
220185

@@ -225,21 +190,20 @@ case object BotWar extends Game
225190
// (b) deterministically incremented (beneficial for testing purposes)
226191
val randomSeed = System.currentTimeMillis.intValue // or: round
227192

228-
factory.createInitialState(randomSeed, plugins)(executionContextForUntrustedCode)
193+
factory.createInitialState(randomSeed, entityControllers, executionContextForUntrustedCode)
229194
}
230195

231196

232197
/** Starts a headless game simulation using the given plug-in collection.
233-
* @param plugins the collection of plug-ins to use as control function factories.
198+
* @param entityControllers the collection of plug-ins to use as control function factories.
234199
* @return the initial simulation state
235200
*/
236201
def startHeadless(
237-
plugins: Iterable[Plugin.FromJarFile],
238-
roundConfig: RoundConfig
239-
)(
202+
entityControllers: Iterable[EntityController],
203+
roundConfig: RoundConfig,
240204
executionContextForUntrustedCode: ExecutionContext
241205
) : SimState = {
242-
val gameConfig = Config.create(roundConfig.permanent, roundConfig.roundIndex, plugins, roundConfig.argMap)
206+
val gameConfig = Config.create(roundConfig.permanent, roundConfig.roundIndex, entityControllers, roundConfig.argMap)
243207

244208
// update the game configuration based on the plug-ins that are loaded
245209
val factory = BotWarSimulation.Factory(gameConfig)
@@ -249,19 +213,14 @@ case object BotWar extends Game
249213
// (b) deterministically incremented (beneficial for testing purposes)
250214
val randomSeed = System.currentTimeMillis.intValue // or: round
251215

252-
factory.createInitialState(randomSeed, plugins)(executionContextForUntrustedCode)
216+
factory.createInitialState(randomSeed, entityControllers, executionContextForUntrustedCode)
253217
}
254218

255219

256220
/** Dump the game-specific command line configuration options via println.
257221
* "... -x 100 -y 100 -steps 1000" */
258-
def printArgList() {
259-
Config.printArgList()
260-
PermanentConfig.printArgList()
261-
Display.printArgList()
262-
Renderer.printKeyboardCommands()
263-
264-
println(" -maxfps <int> maximum steps/second (to reduce CPU load; default: 50)")
265-
}
222+
def cmdArgList =
223+
Iterable("maxfps <int>" -> "maximum steps/second (to reduce CPU load; default: 50)") ++
224+
Config.cmdArgList ++ Display.cmdArgList
266225
}
267226

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scalatron.botwar
2+
3+
import scalatron.core.Game
4+
5+
/** This is the class that will be extracted from the game plug-in by Scalatron in order to obtain a
6+
* game factory function.
7+
*/
8+
class BotWarFactory {
9+
/** This is game factory function that Scalatron will use to instantiate the game instance. */
10+
def create() : Game = BotWar
11+
}

0 commit comments

Comments
 (0)