One of the dev teams at work is also using fogbugz, but we haven't been able to validate the value of EBS, possibly because I'm the only one interested enough in both programming and planning to follow the process in a disciplined way. One of the problems of the method is that it basically requires a waterfall design process, which isn't very popular these days in professional circles.
I am curious to see whether my use of EBS for the development of WorldConquest will validate the nice ideas upon which EBS is built.
My time estimates for implementing simultaneous turns in the game have shown to be wildly off-target. I thought writing to state update function would take 3 hours. I am now reaching 16 hours, and I am still in the testing phase.
It turned out executing orders in parallel can be tricky due to conflicts. For instance, how do you handle a melee attack order and a regular move order. The first order causes the attacking unit to move towards the targeted unit and attack it when it reaches it. But what happens if the targeted unit has an order to move away? The same kind of issues arise for units embarking and disembarking, if the transporting unit moves or is destroyed.
There are quite a few ways to resolve these issues, solving is really just a matter of deciding. Still, having a clear understanding of the design before implementing it is necessary, and I find that using data-flow graphs really helps. I use yED to draw graphs. This tool can redo the layout of graphs all by itself, a feature I cannot live without. It really beats drawing on paper or drawing on screen without automatic layout.
I ended up with the design below:
Boxes are operations, octagons are data. It's not a very complex process, but trying to implement it without a proper design (as I was hoping to do at first) is not an option.
Here is the implementation of the function that puts all parts together:
let update (gs : GameState) (orders : Order[][]) = let damages = seq { for player in 0 .. gs.player_units.Length - 1 do let player_id = PlayerId player let attacks = extractAttackOrders gs.player_units.[player] player orders.[player] yield! computeDamages gs player attacks |> accumulateDamage } |> dict let getDamages u = match damages.TryGetValue(u) with | false, _ -> 0.0f | true, x -> x let gs = applyDamage gs getDamages let dead_units = getUnitDeaths gs damages let isDead u = dead_units.Contains u let player_units = [| for player in 0 .. gs.player_units.Length - 1 do let player_id = PlayerId player let units = gs.player_units.[player] let orders = orders.[player] let isThisDead u = isDead(player_id, u) let move_orders = extractMoveOrders units player orders |> filterDeadMoveOrders isThisDead let early_disembark, late_disembark = extractDisembarkOrders units player orders let late_disembark = filterDeadDisembarkOrders isDead player late_disembark let disembark_orders = Array.concat [early_disembark ; late_disembark] let embark_orders = extractEmbarkOrders units player orders |> filterDeadEmbarkOrders isDead player let units = applyMoves move_orders units yield units |> growUnitTree embark_orders disembark_orders |> shrinkUnitTree embark_orders disembark_orders |] { gs with player_units = player_units }
I have found that comprehensions and the pipeline operator |> are very nice tools to process arrays of units.