huiwang / codingame-scala-kit

Create Better CG Bot in Scala
23 stars 15 forks source link

GameLoop might be a very bad idea #51

Closed huiwang closed 7 years ago

huiwang commented 7 years ago
class GameLoop[Context, State, Action](
                                        gameIO: GameIO[Context, State, Action],
                                        myPlayer: GameBot[State, Action],
                                        accumulator: GameAccumulator[Context, State, Action],
                                        turns: Int = 200
                                      ) {
  def run(): Unit = {
    val time = System.nanoTime()
    val initContext = gameIO.readContext
    CGLogger.info("GameInit elt: " + (System.nanoTime() - time) / 1000000 + "ms")
    (1 to turns).foldLeft(initContext) {
      case (c, turn) =>
        val state = gameIO.readState(turn, c)
        CGLogger.info(state)
        val time = System.nanoTime()
        val actions = myPlayer.react(state)
        CGLogger.info("GameReact elt: " + (System.nanoTime() - time) / 1000000 + "ms")
        gameIO.writeAction(state, actions)
        accumulator.accumulate(c, state, actions)
    }
  }
}

GameLoop was originally designed as a rule-tme-all control strcture for all kinds of games. Thanks to the strong contract, we can always model a game with IO, State/Action, and Bot layer. This allows us to have well-defined traits.

However, the strong contract becomes a limitation in cases where we have to adapt the game modeling to meet the contract. The adaptation is often ugly. For example, in the wondev contest, in addtion to the static context, we must also include historical information in the state to clear the fog of war. Consequently, the state object aggregates many information of different nature.

Instead, we should drop such kind of control strucutres and let the develop choose one best for the specific game. Take the wondev example again, I have a control strucutre as follows

object Player {
  def main(args: Array[String]): Unit = {
    CGLogger.current = CGLogger.info

    var turn = 0
    val initContext = WondevIO.readContext
    var previousState: WondevState = null
    var previousAction: WondevAction = null
    var previousOppoScope: Set[Set[Pos]] = null
    while (true) {
      val state = WondevIO.readState(turn, initContext)

      val oppoScope = WarFogAnalysis.restrictOppoScope(
                        state, previousState, 
                        previousAction, previousOppoScope)
      val clearedState = WarFogAnalysis.removeFog(state, oppoScope)
      val action = MinimaxPlayer.react(clearedState)
      previousState = state
      previousAction = action
      previousOppoScope = oppoScope

      WondevIO.writeAction(state, action)
      turn += 1
    }
  }
}

This way, I have a state object which is quite consistent(no mixing of different kind of infomration). It is still debuggable and benchmarkable. The complexity on the accumulator is reduced dramatically thanks to some mutable vars. What might please @tyrcho is that we don't have to implement anymore the IO, State, Bot in the retricted way (extending predefined traits).

tyrcho commented 7 years ago

I'm fine with both designs. I like the first one because it handles the turns and logs for me, but this can also be done easily by duplicating a short sample like your second code.

I'm wondering if things like implementing referees and offline arenas would have a stronger impact on our design.

huiwang commented 7 years ago

@tyrcho I managed to run offline arenas with brutal test and it has no impacts on the design.

tyrcho commented 7 years ago

@huiwang excellent news ! I'll try and have a look when possible, but I'm travelling most of August so no promises :)