Milestone: Villages
Almost one year ago I started on this project. It still looks promising, I’ve found work-arounds for all road blocks and nearly all abstractions are in place and working. That means I from now I build on code I’ve written myself. No one is left to blame except the guy in the mirror.
Why is this a milestone? Well, running the demo is clearly not impressive, but is proves a few important things:
- The triple store + query language deals with all 14 civilizations
- 8 Hannibal run simultaneously without logical interfering
- Map processor reliably finds free places for new buildings
- The economy model prioritize units by cost or other properties
- The groups and their DSL just workTM
- The event system properly identifies external events and fires the internal ones
- The 0 A.D. Bot API is stable enough to rely on, even in Alpha stage.
- Hannibal and SpiderMonkey 31 are a good team :)
The last point deserves expanding: The current 0 A.D. SVN was just updated from SM 24 to SM 31 and performance improvements are highly visibly. At first I thought the internal profiling broke, because it reported “0 msec” far more often than usual, but that was not the case. The initialization of an instance of Hannibal now takes around 30-40% less time. I have to make separate tests, find out what changed and report back here. One thing cannot be repeated often enough: Make use of function inlining!
Here’s an example:
obstructions
.blur(radius)
.processValue(v => v < 32 ? 0 : v)
.addInfluence(coords, 224)
;
It works on the grid objects which support method chaining. The arrow function processValue() throws at the grid is called on every pixel and that line takes less than 1 msec for a 512x512 map on my years old machine. There is clearly no function overhead involved and I even started to review a few modules to exploit this and achieve more readable code in the process.
By now you can see the village has no structure and buildings are randomly rotated. The algorithm at work is a fail safe. It can find a place close to a given point if there is at least one available. Building a village is a packing problem and these suffer from NP !== P. I haven’t found a fast one for rotated rectangles yet, but the separating axis theorem is useful. So, Hannibal can try to layout its village and choose the fail safe as last resort. Actually there are not that many requirements: Fields should be very close to a dropsite, towers are best organized as corners of an equilateral triangle and on some maps the concept of streets is needed to avoid blocking the exit points of a village. And eventually if terrain allows a strong wall.
So here’s my road map to the next test release:
- Structured villages with all default buildings + walls or palisades
- All village supporting groups finished
- Attack groups using a flow field to maintain distance and health
Actually I plan to make this a full release which supports the sandbox difficulty. Sandbox means Hannibal will not attack and new player can spy on the bot to develop the micro-management to build a village fast.
The release after that might even include ships :)
I’d like to stress here Hannibal is Open Source and everybody is welcome to contribute. Conceptually it is a Bot Development Kit for 0 A.D. with an engine and autonomous groups running on top. The groups are scripted with an easy language. This test release is about building a village and here is the most important piece from the builder group. It is called when the economy found a place for a building or assigns a unit to the group.
// a request was successful
}, assign: function assign (w, item) {
w.objectify("item", item);
// keep requesting units until size
w.units.on
.member(w.item)
.lt(w.units.count, w.units.size)
.request()
;
// the first unit requests structure to build, exits
w.units.on
.member(w.item)
.match(w.units.count, 1)
.match(w.buildings.count, 0)
.buildings.do.request()
.exit
;
// got the foundation, all units repair, exits
w.buildings.on
.member(w.item)
.match(w.item.foundation)
.units.do.repair(w.item)
.exit
;
// got the building, check order next, exits
w.buildings.on
.member(w.item)
.match(!w.item.foundation)
.lt(w.buildings.count, w.buildings.size)
.request()
.exit
;
// got unit, send to repair
w.item.on
.member(w.item)
.gt(w.buildings.count, 0)
.repair(w.buildings)
;
You’ll find me at the 0 A.D. forum. Many thanks to everyone active over there.