Automatic Ship Design for the AI

Programmers discuss here anything related to FreeOrion programming. Primarily for the developers to discuss.

Moderator: Committer

Message
Author
User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#16 Post by Dilvish »

I like Cjkjfnby's filter approach #1. Combining that with Geoff's advice and my earlier advice, we'd have

Code: Select all

WEAPONS = [fo.shipPartClass.shortRange, fo.shipPartClass.missiles, fo.shipPartClass.fighters, fo.shipPartClass.pointDefense]
and then rather than test for name equality, the key line in part_is_weapon() would read

Code: Select all

return fo.getPartType(partname).partClass in WEAPONS
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

Morlic
AI Contributor
Posts: 296
Joined: Tue Feb 17, 2015 11:54 am

Re: Automatic Ship Design for the AI

#17 Post by Morlic »

Dilvish wrote:What we do have is a way to assess if a given Ship Design is currently buildable at a given location. (In the relatively near future I'll expose similar functions for the AI for Hulls and Parts which will slightly simplify the following process but it won't really make a big difference.) This is what the current production code does for the predetermined AI designs. Note that the AI code assesses availability on a ResourceGroup basis -- if the AI empire is blockaded and cut in two, your best ships might not be buildable at any location in one of those halves and you still want to be sure to have designs that could be built in that half (they may still be too poor to actually want to build but that can't be decided without knowing what the possibilities are). First assess the buildability of the hulls, using a near-empty design-- nothing but the hull, at each of the locations. Next, at each location and using a hull buildable at that location (if you choose something like an SH_STANDARD it should work for all potential locations and be fine for all parts except core slot parts), go through all the parts and test a design using that hull with only a single part in it to test if that part is buildable at that location. For now, you could streamline this with the knowledge that the only current parts with specialized location requirements are (I think) a few armors and the colony pods.
I think that workaround should do.
Dilvish wrote: Nothing faster comes to mind immediately, but I would think it better to compare the part.partClass directly rather than its name -- the name is somewhat more at risk of changing even if the essence of the partClass does not.
Makes sense, I will do that.
Cjkjvfnby wrote: If you need to filter, you can 'filter'
Dilvish wrote: I like Cjkjfnby's filter approach #1.
I found the "filter" function but read somewhere that "filter" is considered (somewhat) bad Python style and list comprehension would be preferable from a style point of view:
If the expression is simple enough that it can be expressed with a list comprehension, then the Python community prefers that you use list comprehensions.
Now obviously the question would be whether or not the expression is considered "simple" or if it is already "complex" considering the fact that it is basically a double list comprehension. The advantage in this approach obviously is that it fits into a single line for a relatively simple task which is rather self-explanatory by context.

Not that it matters too much in this example but since I am just getting started with Python I guess it doesn't hurt to discuss these nuances briefly so I adopt the good habits instead of the bad ones.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#18 Post by Dilvish »

Morlic wrote:Now obviously the question would be whether or not the expression is considered "simple" or if it is already "complex" considering the fact that it is basically a double list comprehension.
Hmm, I suppose the thing that caught my eye the most with the filter # 1 approach was the benefit of using the part_is_weapon() test function, which allows the embedded try:except. FO is still alpha stage and periodically buggy. If empire.availableShipParts somehow gave an invalid partname the plain list comprehension approach would likely crash. Of course, the part_is_weapon() test function could be used in a single-level list comprehension instead of being used with filter(). I'm far from a python style expert myself; what you quoted sounds sensible but let's see what Cjkjvfnby has to say as well, he's a bit more focused on that sort of thing than I am.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Cjkjvfnby
AI Contributor
Posts: 539
Joined: Tue Jun 24, 2014 9:55 pm

Re: Automatic Ship Design for the AI

#19 Post by Cjkjvfnby »

Morlic wrote:
Dilvish wrote: I found the "filter" function but read somewhere that "filter" is considered (somewhat) bad Python style and list comprehension would be preferable from a style point of view:
If you have lambda in filter, list comprehension will be better.
List comprehension is more flexible (It can do both map and filter in same cycle).
In each current case implement both and see which look better for you. In example it looks good for me, but if you need to get partType objects as result, list comprehension will be better.

My opinion about filter map and reduce is that it is useful functions, but many people does not use them properly. This is real reason why it is bad style.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

Morlic
AI Contributor
Posts: 296
Joined: Tue Feb 17, 2015 11:54 am

Re: Automatic Ship Design for the AI

#20 Post by Morlic »

So a little update on my progress:

I am currently trying to build a new class-based, more powerful framework for the shipdesign process.

Basically, I have a base class that simulates the shipdesign and has a targetFunction() to evaluate that design. The class itself is then able to (heuristically) optimize the design. The different shipClasses the AI knows are children of this class and have their own (default) assessment function.
It is possible to redefine (in the future maybe also simply refine) the assessment function to fit our specific needs (need at least 5 fuel, build time under 4 turns...).

I would like to never evaluate a design outside of this framework. So some other part of the AI code thinks: I might want to build a scout... What scouts can I build which fit these needs? It will get the best design choice(s) and its rating(s) and then decide whether to build that one or not. So any rating of the design should be directly done at creation time.

The current framework progress is attached for reference.
1) Are there any objections to this approach?
2) Any important considerations I missed or should consider for the future?
3) Any wishes or ideas what the framework should be capable of?


Also:
4) Any idea on how to read out the effects of the "capacity = 0" part stuff like detectors? I guess I could once again make a workaround using the TestDesigns of these parts which exist anyway for the resource-requirements... Still wondering if there is a more reasonable approach available at the moment.
Attachments

[The extension patch has been deactivated and can no longer be displayed.]

If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#21 Post by Dilvish »

I haven't looked over the code yet, but the class approach sounds fine to me. I also have a other few initial responses to some of your comments and questions:
Morlic wrote:I would like to never evaluate a design outside of this framework.
For combat ships, at least, as we've discussed there is rating evaluation done currently in the AIstate code, and I indicated I thought you ought to use that. Are you meaning you want to relocate that code here? Could you elaborate on just what you mean, and why?
..So any rating of the design should be directly done at creation time.
It is sounding to me like you mean creation time of the Design. I see ratings as too dynamic to simply calculate at creation and then leave fixed. Ratings (at least combat ratings) depend a lot on the opponent being faced. Right now the AI just has the beginnings of code to deal with this, tracking the enemy designs encountered, choosing one of those as representative of current enemy forces, and rating its own designs in light of that. There are a number of ways that can be further augmented; such as considering the defenses of enemy planets as well as ships, perhaps building a mix of different designs optimized for different types of targets. Those different combat purposes could still be handled as different subclasses in your framework, but I think it highlights he dynamic nature of ratings. Another example would be that the value of armor in troopship ratings should take into account whether or not enemies have researched the various levels of Mine technology (right now the AI just automatically starts using armored troops at later stages, but it would be nice for it to be a more nuanced decision).
Any idea on how to read out the effects of the "capacity = 0" part stuff like detectors? I guess I could once again make a workaround using the TestDesigns of these parts which exist anyway for the resource-requirements... Still wondering if there is a more reasonable approach available at the moment.
Someday (I think in the not too distant future) those parts will indicate their correct base capacity. Trying to determine it from TestDesigns won't really work because the ships actually have to be made to read off the correct values for these types. Also, species can affect the result, and the hull can too (at least currently for detection). I suggest for now you just hardcode it. Species AI tags for detection will also need to be added, as has been done for Weapons, Shields, etc.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

Morlic
AI Contributor
Posts: 296
Joined: Tue Feb 17, 2015 11:54 am

Re: Automatic Ship Design for the AI

#22 Post by Morlic »

Dilvish wrote:For combat ships, at least, as we've discussed there is rating evaluation done currently in the AIstate code, and I indicated I thought you ought to use that. Are you meaning you want to relocate that code here? Could you elaborate on just what you mean, and why?
Yes, I found and at least partially used the code there. Plans are to move anything related to assessing a ship(design) to this new module. First, this is for obvious structuring of the code. If I want to deal with shipdesigns, I want to look at a single module and not at 3 of them.

Second, the plan is to have a framework which allows this flexible modification of the rating function and a system which allows this for different classes of ships. The code structure I have in mind is not compatible with just copy-pasting the code 1:1 and I wouldn't consider that to be useful either so the existing rating functions could/would be simply reimplemented in the new framework with the new data structures but old formulas (though we might want to discuss these once the framework is finished).
It is sounding to me like you mean creation time of the Design. I see ratings as too dynamic to simply calculate at creation and then leave fixed. Ratings (at least combat ratings) depend a lot on the opponent being faced. Right now the AI just has the beginnings of code to deal with this, tracking the enemy designs encountered, choosing one of those as representative of current enemy forces, and rating its own designs in light of that. There are a number of ways that can be further augmented; such as considering the defenses of enemy planets as well as ships, perhaps building a mix of different designs optimized for different types of targets. Those different combat purposes could still be handled as different subclasses in your framework, but I think it highlights he dynamic nature of ratings. Another example would be that the value of armor in troopship ratings should take into account whether or not enemies have researched the various levels of Mine technology (right now the AI just automatically starts using armored troops at later stages, but it would be nice for it to be a more nuanced decision).
I only meant evaluating at design time as right now when building a new ship, the AI rates existing designs, then chooses the best. With the new framework, it should specify its needs, get a design and then work with what it got. If it decides the design is good enough, it says "addDesign()", otherwise it may ask for different design specifications or decide not to build a ship right now. It would not query existing designs.

I completely agree with your point of the ratings being a dynamic problem. This is one of the major reasons I wanted to build a more powerful.
First, the functions for optimizing and adding a design are independent of the rating function. It is possible to create an instance of the AIShipDesign_Class (let's say military), pass the parts/hull/species and then evaluate it at any point in time.

Code: Select all

basicMilitaryDesign = AIShipDesign_Military()
basicMilitaryDesign.updateHull(hullname)
basicMilitaryDesign.updateParts(partnamelist)
basicMilitaryDesign.updateSpecies(speciesname)
rating = basicMilitaryDesign.evaluate()
Once completed, it should be possible to pass any kind of additional information. For example, we could give information about the enemy (some omnipotent struct/class not yet implemented):

Code: Select all

enemy = Enemy("shields",4,"averageWeaponStrength",7)
basicMilitaryDesign.updateEnemy(enemy)
and the code would then calculate the effectiveWeaponStrength by subtracting enemy shields or evaluate the value of shields differently because of known enemy weapon strength as it is right now.
However, passing should be easy, readable and optional. The rating functions then pick whatever information is available and useful to them and calculate the rating.

So for your example with the troops:

Code: Select all

enemy2 = Enemy("mineLevel",2,"TargetPlanet",pid)
troopDesignVsMines = AIShipDesign_Trooper()
troopDesignVsMines.updateEnemy(enemy2)
designID,planetID = getBestDesign(basicTroopDesignVsMines)
The rating function then would see that there are mines and maybe have some minimum value of armor or whatever the specific details then finally are. It could now also somehow consider the distance to the target planet so we prefer building it closer to the enemy if the designs do not differ too much.
If we didn't specify these information, there is no distance information and thus only design matters. We would implicitly assume that there are no mines present and have a more simple rating such as

Code: Select all

rating = self.troops / self.productionCost
Other things that I will try to implement is stuff like minimum fuel, maximum production time and so on:

Code: Select all

limitingRequirements = Requirements("minFuel",5,"maxProductionTime",6,"maxProductionCost",PPperTurn*6)
myDesign.updateRequirements(limitingRequirements)
getBestDesign(myDesign)
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#23 Post by Dilvish »

Morlic wrote:Plans are to move anything related to assessing a ship(design) to this new module.
Sure, now that I see you're structuring this a module focused on all aspects of ShipDesigns, it's is a sensible place for at least the ShipDesigns portion of the ratings code (which is most of it). Keep in mind, you'll also need to adjust the AI state code to the functions in your new module then.
I only meant evaluating at design time as right now when building a new ship, the AI rates existing designs, then chooses the best. With the new framework, it should specify its needs, get a design and then work with what it got. If it decides the design is good enough, it says "addDesign()", otherwise it may ask for different design specifications or decide not to build a ship right now. It would not query existing designs.
Hmm, it sounds like you're using the term 'it' to refer to some portions of the AI but not others, which makes it harder to understand your intent. I am most confident that you mean that the production portion of the AI would request a design from your module without caring whether it was a preexisting design or not. Looking at your code, I see that the ShipDesigns module does not appear to decide at any point whether or not the (hull, parts) combo it recommends corresponds an existing ShipDesign, and instead appears to be leaving that issue to the productions code, which seems a bit odd to me. I would think that this follows the same kind of logic as where to locate the ratings code-- that the ShipDesign module would have the code for actually registering the designs via the freeOrionAIInterface. But I guess there is no rush with that. In the meantime though, having the ProductionAI to call into ShipDesign.AddDesign() where the latter essentially just calls back to ProductionAI.AddDesigns(), seems excessively roundabout.
Once completed, it should be possible to pass any kind of additional information. For example, we could give information about the enemy (some omnipotent struct/class not yet implemented):

Code: Select all

enemy = Enemy("shields",4,"averageWeaponStrength",7)
basicMilitaryDesign.updateEnemy(enemy)
This approach of having things like planetID, species, and enemy be attributes of the ShipDesign also catches my eye, as opposed to having them simply be passed in to the rating evaluation function. I guess if you are intending for the ShipDesign objects to be very short lived, simply for the duration of a single production decision, then that might be more efficient, but I guess I'll have to think about it more and see better how you envision this ShipDesign module working with the other modules.

I've also had a chance to review the code some and have some comments now. I'll start by noting it's looking like a very good start; my comments focus on things you noted as TODOs that I think are important to cover now, or that I have questions about or would advise changing, but don't mistake that for an overall negative.

Regarding error handling, I see at least one instance of Raise Exception in optimizeDesignFiltered(), which we generally want to avoid. Keep in mind we might have players trying to customize their content scripts but perhaps not quite be up to the task of updating the AI for it. So rather than having the AI totally die in the face of unexpected content we want it to continue on as best it can. Instead we try to have the routine fail gracefully in the minimal way possible and report the log to log and as a chat message to the human player via freeorion_tools.print_error(). Feel free to use print_error anytime you think it's important to make sure the error message doesn't get overlooked.

Regarding the approach with updateTestDesigns()-- At least long term, and now since you are already trying to handle Core slots, it seems like this function should be called once for each location? (That should probably be noted in the docstring.) Or else you're back to having to have a test design for every single (hull, part) combo, aren't you? (More discussion on that below.) I see you have a TODO about testing parts with other hulls, so here's my proposal for that: the function would be updateTestDesigns(location_id). Then, I think that updateTestDesignsForShipHulls(location_id) should be called before updateTestDesignsForShipParts(), to get a list of hulls buildable at the present location (and explained further below):

Code: Select all

    local_hulls = updateTestDesignsForShipHulls(location_id)
    updateTestDesignsForShipParts(local_hulls)
Regarding updateTestDesignsForShipHulls()-- I think it will be helpful if this function compiles a list of those hulls which are buildable at the designated location (preferable with SH_STANDARD being first in the list if it is buildable locally), and returns that for later use by updateTestDesignsForShipParts() and perhaps other parts of the code as well if we try to optimize the process a bit more. It would still do the full update of hulls test designs independent of local buildability.

Regarding updateTestDesignsForShipParts(local_hulls)--
  • As I had noted previously, SH_STANDARD is not compatible with core slots, which you're already trying to handle. And of course future possibilities are limitless. So although it's a good first choice to check we can't just assume it will be a valid hull to test the part with; using this local_hulls parameter helps solve the problem nicely.
  • having the test designs named as "%s_%s"%(basename,partname) is probably not reliable long term, because at least in the future it might be possible that, for example, the same part might need to be checked with two different hulls at two respective locations. I think that one possible minimal change would be to name the designs "%s_%s_%s"%(basename,partname, hull_name), then your initial test instead of simply checking

    Code: Select all

    if "%s_%s"%(basename,partname) not in testDesignNames
    could instead be something like

    Code: Select all

    if not any ["%s_%s_%s"%(basename,partname, hullname) in testDesignNames for hullname in local_hulls]
  • The "for part in newParts:" loop would then have a subloop "for testhull in local_hulls:", with a break after the first success. Also, I think the first try:except block in this loop is not needed as a try:except if you change the body to be something like

    Code: Select all

                mutual_slot_types =  set(part.mountableSlotTypes).intersection(slotlist)
                if mutual_slot_types:
                    slotIndex = slotlist.index(mutual_slot_types[0])
                else:
                    continue
                partlist[slotIndex] = part.name
                print "Partlist for Test Design %s: "%(designName),partlist
  • In ShipDesign.optimizeDesignFiltered()-- You note at the outset of this, "For performance reasons, fill each slot with the currently optimum part. ToDo: Maybe add a better algorithm", but I think this is not just a maybe but is actually critical. The relative value of armor versus weapons might easily depend on whether and what type of shield is present, and of course the relative value of a shield is heavily dependent on what other parts are present. Although for most current hull types the internal slots for shields will be at the end of the list, that doesn't always hold and we certainly don't want to assume it. I agree that certainly the raw permutation approach is intractable-- with up to at least 18 external slots as a possibility, each of which could hold one of 4 weapons or 7 armors, we're up to 11^18 designs = 5.6e+18 designs before we've even gotten to considering shields, rather a bit much. Let me talk through a couple ideas and we'll see just how general we can get this without insane complexity. I'm going to focus on combat ships because they are the most complex and the most important for this feature; if we get a decent handling for them we can either generalize to other ship types or just have a different handling for other types.
    1. I think the first definite change would be to stop handling the slots in the order they are encountered in hull.slots. First do a pass to determine how many of each slot type you have. No matter what approach we make, this will need to be part of it. After figuring out what parts will be used, it will be reasonably simple to put them into an order that is compatible with the hull slot order.
    2. After that, a minimal change that might just possible make this work out well enough to really use (at least with current content), would be to simply handle the external slots first, then the core slot and then the internal slots (or perhaps external, internal then core). That would at least let the shield decision take into account the weapon and armor lineup, which is currently quite important. The viability of this approach is still somewhat dependent on current content characteristics (weapons being external slots, shields internal), but at least in some important ways notable less so than the current proposal.
    3. One step more general than the above might be to assess what part types go into what slot types and then make sure we deal with the slots for shields after the slots for weapons and armor, but I think that has some weaknesses and is just a small improvement and I think we can do much better.
    4. Right now you have some code to eliminate truly redundant parts, so that if the empire has MD1-4 available it will only assess MD4, not the other three. Are there any other simplifying assumptions about part redundancy/optimality we can make that would be reasonable? For example, currently we only rule out a lower capacity armor if there is a higher capacity one that can produced at least as quickly and for no greater cost. After we've done that elimination, what if did some further elimination by starting with the highest capacity armor and only considered others only they if they could be produced more quickly or offered a better capacity/cost ratio. We could also eventually add a check against being stuck only with a ridiculously powerful, ridiculously expensive part, but I think that's low priority. For the current armor parts this would have us considering (for combat ships at least) only the single best armor part buildable at that location, which is I think currently the right approach and also the approach the vast majority of players would take. So for now, at least, I think that would be a very helpful simplifying assumption. Similarly, I think we can get a little bit more reduction on weapons, and a little bit really goes a long way. I'd allow a bit more leeway on timing here-- start with the list being just the highest damage weapon, and then only add another weapon if it has a better damage/cost ratio. Eventually we'd want to add a consideration for weapon build time but I'd leave that off for now. So if you have Laser 2, you'd consider bother Laser 2 and MD 4. Once you had Laser 3 you'd never consider MD again. At Plasma 2 you'd consider it and Laser 4 (if you didn't skip it), but once the empire got Plasma 3 it would never consider Laser again. So, at any point you have at most two weapons to consider.
    5. Besides those redundancies, there is the issue of part stacking. Right now, for evaluating designs, I think we have little choice but to hardcode the fact that shields do not stack, nor do detectors. Longer term we are talking about more support so that it could be determined directly, but for now the simple hardcoded rule is reasonable I think. So we might consider two different shield parts for a design, but we would not consider a design with two shield parts in it.
    6. Let's tentatively also assume we want to fill all the external slots. I think we shouldn't assume that for the other slot types but for now at least I'm game to try it for externals. So, relying on the above simplifications, I think it will be reasonable to consider absolutely all qualifying designs (at least with anything close to the current content). So, we have determined that our hull supports E external slots, I internal slots, and C core slots. For each group we care about the combination of parts it can support, not the permutations. So (for combat ships) for external slots we have at most two weapon parts to consider and one armor part. As for the weapons, I think it reasonable (at least for now) to assume that in a given design we'll choose either weapon A or weapon B but not a mix of weapon A and weapon B. So it boils down to how many (1 to E) of the E external slots will go to weapons, with the remainder going to armor. So that is a total of 2E possible combinations for the external slots. For the set of I internal slots, we'll have either zero or one shield selected from 5 types, and the rest selected from up to maybe 4 or so choices of fuel, engine. So that gives us something on the order of magnitude of 5I possibilities for internal slots, giving us a grand total of 10*E*I designs altogether. For the biggest of hulls that puts us somewhere on the order of magnitude of about 1000 designs to assess, so perhaps 5k-10k possible designs total (on any given turn) across all the hull types, which I think is probably tractable, particularly if we do some intelligent caching of results so it doesn't necessarily have to be recalculated for every single location.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

Morlic
AI Contributor
Posts: 296
Joined: Tue Feb 17, 2015 11:54 am

Re: Automatic Ship Design for the AI

#24 Post by Morlic »

Dilvish wrote:
Hmm, it sounds like you're using the term 'it' to refer to some portions of the AI but not others, which makes it harder to understand your intent. I am most confident that you mean that the production portion of the AI would request a design from your module without caring whether it was a preexisting design or not.
As of he current code state, yes.

But - the request could also be some part of the decision making code: I might want to invade Planet X. Can I build a suitable troop ship for that? Oh, darn I can't, better make some other plans then.

Once finished, the framework should be capable of dealing with all kind of questions in this regard.
Looking at your code, I see that the ShipDesigns module does not appear to decide at any point whether or not the (hull, parts) combo it recommends corresponds an existing ShipDesign, and instead appears to be leaving that issue to the productions code, which seems a bit odd to me. I would think that this follows the same kind of logic as where to locate the ratings code-- that the ShipDesign module would have the code for actually registering the designs via the freeOrionAIInterface. But I guess there is no rush with that. In the meantime though, having the ProductionAI to call into ShipDesign.AddDesign() where the latter essentially just calls back to ProductionAI.AddDesigns(), seems excessively roundabout.
Yeah, see ToDo list. It is only a very lazy implementation so I can care about other stuff for now and do not have to modify the current ProductionAI code. Of course this functionality is to be moved (or reimplemented) in the ShipDesign module. At least the code has the correct functionality instead of just "pass" :)
This approach of having things like planetID, species, and enemy be attributes of the ShipDesign also catches my eye, as opposed to having them simply be passed in to the rating evaluation function. I guess if you are intending for the ShipDesign objects to be very short lived, simply for the duration of a single production decision, then that might be more efficient, but I guess I'll have to think about it more and see better how you envision this ShipDesign module working with the other modules.
If we make sure that also in the future we will always deal with the design process at one particular place, I guess an approach like

Code: Select all

def ratingFunction(self,**kwargs):
    planetID,species,enemy,requirements,stuff1,stuff2,stuff3,stuff4 = self.interpret(kwargs)
    (...)
is viable and wouldn't cost too much readability. The interpretation stuff can be done in the base class so that would be a one-time implementation the user and future AI developer wouldn't need to worry about for new rating functions.
If we might want to split the decision-making and the actual building stuff, then the attribute-based approach I think is more reasonable as we can just set some information in one part of the code and some other information in some other part of the code and pass the AIShipDesign object around instead of a list of arguments.

I will rework the interface and some code structure anyway once all functionalities are implemented, I guess. There are all the issues like the core parts which require rewriting some part of the code and make it more reasonable to move some functionality around in various functions.
So I guess I will wait until everything is implemented and then take another look at whether or not we need to have this flexibility or not.


Regarding the core-slot-issue:
I think the testdesigns for hulls can and should remain independent of the location. The checking whether I can build a hull is done in canBuildHullOnPlanet(hullname,planetID). Currently the implementation is

Code: Select all

availableHulls = [hull for hull in empire.availableShipHulls if canBuildHullOnPlanet(hull,planetID)]
(see getBestDesign()). I guess I could move this line of code to a new function getLocalHulls(planetID). In this way, we only have to call the updating function once per turn.

As far as the ship parts are concerned: This might be a reasonable approach to cover core parts and future content. It is similar to what I had in mind, I will definitely consider that.



Regarding the redundant parts:
We surely can add additional filters. I am not sure we always want to use them (you convinced me previously in this thread about not having a really best weapon etc.). If we want to build some fast warship to simply track down scouts, we do not care much about weapon strength. A cheaper design would be more helpful as long as it can take down scouts. If MD4 is enough, we do not want to build a deathray.

We could also filter the hulls - for big warships we generally want as many external slots as possible and a decent structure, other ship types might prefer internal hulls or only slots/cost (troopers).
I think it would be a good idea to implement a variety of filters in the base class and have attributes that control them. The specialised classes then have only the appropiate filters activated.

Regarding the stacking:
This is currently hardcoded in the updateStats(self) function:

Code: Select all

        shields = [part for part in self.parts if part.partClass in SHIELDS]
        if len(shields) == 1:
            self.shields = shields[0].capacity
        else:
            self.shields = 0
This ensures that we do not get an effect out of this but the cost still increases. So the rating will be worse with 2 shields. While it would be more optimal performance-wise to check so before adding the part, this should be an acceptable temporary implementation until the optimizing algorithm is optimized (then I would move it there).



Regarding the optimizing algorithm:
If we want to make all these assumptions you propose, we can reduce the complexity even further by having an initial guess and then have some pseudo-Newtonian approach of finding the (local which for simple enough functions is also absolute) minimum of the targetFunction. We could make the assumption that we will have roughly a ratio of 1:1 weapons and armour, then adjust from there. In fact, considering we know the target function, we could even give an analytical solution directly (for weapons/structure at least).

However, the problem as you pointed out is that this only considers the current content and makes very specific assumptions.

Maybe a more general approach would be to start with an educated initial guess and then adjust the design from there: Try to change single parts until we do not find an improvement anymore.
If the initial guess is good enough, this will not require too many iterations. However, if we suddenly have some unexpected new parts (weapons in internal slots), the AI would still be able to use them without changing the algorithm.

The initial guess could be something along your ideas and/or some estimation based on previously best-rated designs.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#25 Post by Dilvish »

Regarding the redundant parts: We surely can add additional filters. I am not sure we always want to use them (you convinced me previously in this thread about not having a really best weapon etc.).
It looks to me like the approach I outlined above is totally in line with my previous statement about not assuming there is a single best weapon, shield, armor, etc., partly because this approach checks the issue location-by-location, and partly because it allows for multiple best parts in each category (a max capacity best and a max capacity/cost best, which are not always the same). If you think there is any notable way that the approach fails to identify an important aspect/kind of best part (at least for weapons and armor), then please let's discuss that more. It could be that the extra filtering won't be absolutely needed, but I suspect it's likely to be helpful on many machines.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

Morlic
AI Contributor
Posts: 296
Joined: Tue Feb 17, 2015 11:54 am

Re: Automatic Ship Design for the AI

#26 Post by Morlic »

As I said - it will mostly depend on the ship type we want to build thus my idea to make the filter controllable.

Sure, for big warships your suggestions seem fine. Most certainly, the few exceptions to this rule for warships are not in the AI code yet.

But what if we are limited in our building time? There is an enemy fleet approaching, this is most likely the final battle. Do we then want to build a big, expensive ship? Maybe it won't be done in time. It would be better to build a warship that actually gets to the battle in time, so we might need cheaper parts - both in time and PP.
So we want to have some option to turn the filter off.


What about other ship types?
We want to build a raider style ship to harass the enemy supply lines.
So we want to have a stealth part, a scanning part and a hull that suits our needs. Maybe also some Bio-terror part. With this setup we could never face a warship (let alone a fleet) in combat. We rely on dodging the enemy and want to do as much damage as possible before we lose the ship. Maybe we can find some unprotected troopers or a scout?
A single MD4 is perfectly capable of handling this task. Why would we waste PP on a more expensive weapon? Why would we care too much about hull strength if a single enemy warship will destroy us anyway?
The cheaper the ship, the more probable we can hurt the enemy economically more than the building of the ship did hurt us.

Maybe we want to arm a scout so it can shut down unarmed enemy scouts? We won't let a scout face a warship, but why not give it a cheap MD4 in the midgame? We certainly do not want to have expensive plasma cannons while sitting on 10 structure.

We want to invade that enemy planet with troopers. The enemy has mines. So there is some threshold value of structure we need. We do not care much about structure otherwise. So why would we build the best armor plate if the cheaper part works as well?
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#27 Post by Dilvish »

Hmm, I had decided my previous reply was getting too long and trimmed it down quite a bit, I guess too aggressively.

We're in agreement that other ship types would have other filters with varying degrees of similarity to these. And that things like 'Scout Hunter' would, if used, probably want a cheaper weapon, and that this could be readily handled with this framework, with 'Scout Hunter' as a different subclass than the main warship. I think a strategy of trying to effectively use such a Scout Hunter would be very tricky for the AI due to the extra 1% cost each such cheap Scout Hunter would add to the cost of every Heavy Battleship. Also, by the time there's a significant variance in weapons to support wanting a specific Scout Hunter design, detection technologies have probably advanced enough that Scout Hunting is not worth much defensively. Not that it's necessarily worthless, and with future content it could be more valuable, but with current content I think there are numerous other areas of AI planning that will give much better returns. It is a possible future strategy, and I think it's great the framework would support it and things like it, but actually trying to implement it would be a pretty low priority for me at least.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Cjkjvfnby
AI Contributor
Posts: 539
Joined: Tue Jun 24, 2014 9:55 pm

Re: Automatic Ship Design for the AI

#28 Post by Cjkjvfnby »

I will try to read this thread.

Lets replace word framework with word module.
I expect each module have some kind of public interface. This is most hard part of our work to specify it.

Implementation is important too, but it can be changed and groomed then we will know that we want.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Cjkjvfnby
AI Contributor
Posts: 539
Joined: Tue Jun 24, 2014 9:55 pm

Re: Automatic Ship Design for the AI

#29 Post by Cjkjvfnby »

If you both don't mind I will make function to create design name/description based on parts and use it for current code.
In any case it need to be done and this can be commited right now without waiting whole framework.
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

User avatar
Dilvish
AI Lead and Programmer Emeritus
Posts: 4768
Joined: Sat Sep 22, 2012 6:25 pm

Re: Automatic Ship Design for the AI

#30 Post by Dilvish »

Cjkjvfnby wrote:If you both don't mind I will make function to create design name/description based on parts and use it for current code.
In any case it need to be done and this can be commited right now without waiting whole framework.
If you have something you want to propose certainly feel free to post it. Currently the design names have a small bit of functionality in (at least potentially) sorting & filtering designs to be checked. That will probably not be done in this new framework, and so trying to make your proposal compatible with that would probably be wasted effort. Is there any particular reason to rush to change the naming?
If I provided any code, scripts or other content here, it's released under GPL 2.0 and CC-BY-SA 3.0

Post Reply