I'll allow myself to write a little essay on adaptation. Well, i'm writing this mostly for myself, cause i need some clarity in my head before proceeding.
Currently the NullBot3's adaptation mechanisms allow gathering information on
usefulness rating of certain components in current game situation.
- propulsions, classified by movement scopes (ground, hover, air).
- propulsions, classified by weakness to certain weapons (tanks, cyborgs, defenses, vtols)
- bodies, classified by dominating armor class (kinetic, thermal)
- weapons, classified by roles (anti-tank, anti-personnel, anti-structure, anti-air).
- weapons, classified by damage class (kinetic, thermal)
Some of this data is inaccurate, but it isn't relevant to the today's topic. In the original NullBot only 4. and partially 1. was available.
Based on this data, NullBot3 needs to make an adaptive decision formulated as a single game object template: a tank design, a cyborg template, a vtol design, or a defensive structure, or a research item, to produce or build right now at that particular moment.
You may say it's easy: just pick the best propulsion, the best body, and the best weapon. But in fact we can't choose those independently, for there are many restrictions imposed:
- Sometimes the desired weapon class, body class or propulsion class is unavailable. For instance, weapons of anti-tank role (rockets, or maybe cannons) are unavailable for quite some time in early game. Or you may have not yet researched hover propulsion. In this case it is unobvious whether you can proceed by making the second best decision (eg. using machinegun as an anti-tank weapon until rockets are available, or prefer to keep upgrading machineguns instead of researching flamers from scratch simply because your enemy switched his tanks from new paradigm bodies to collective bodies) or you need to stop and reconsider the very choice of producing a unit (eg. on a hover-only map you'd prefer to wait for hovers rather than fill your base with wheeled or tracked tanks).
- Sometimes components just won't match. For instance, you may believe that leopard body is better than scorpion body for your VTOLs (since it provides faster reload due to decreased weight, and also has the same armor and only slightly less hp), but you can't put heavy bombs on it, because they will fly too slowly. Sometimes some sorts of weapons are unavailable in form of cyborgs or VTOLs (for instance, a pure rocket player will never had anti-personnel VTOLs or cyborgs), but available as tanks and defenses.
Thus, here is a question:
does anybody have any idea how to write a clean and readable code for making this sort of decisions? What sort of algorithm should be used for making it?
My current idea is as follows. First of all, we throw all absolutely unbreakable restrictions into a single checker function: isValidOption(option). Then, we start with an option of all components being independently best in their category. Then we start a
breadth first search on an oriented option graph (the
Hasse diagram of the partially ordered set of all options, with "better than" as its order) where options are vertices, and arcs are coming from independently-better options to options that are one level worse in one component: for instance, from the (twin AG, panther, tracks) option there will be arcs to (AG, panther, tracks), (twin AG, leopard, tracks) and (twin AG, panther, halftracks). We only proceed our search until all options in our BFS queue are valid; if a certain option we have just pulled out of the queue is already valid, then we don't proceed with searching through it (for it will only lead to worse options), but store it in a separate "result array", which will ultimately contain all
maximal valid options. Once the search is completed, we choose the best of the valid options presented by the search (taken from the "result array") via a certain rating procedure (most likely, options that are farther from the search origin will be valued less).