| Total Complexity | 94 | 
| Total Lines | 385 | 
| Duplicated Lines | 13.51 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like AIs.ais_mutant often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | #  | 
            ||
| 2 | # Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]  | 
            ||
| 3 | #  | 
            ||
| 4 | # This file is part of Outer Space.  | 
            ||
| 5 | #  | 
            ||
| 6 | # Outer Space is free software; you can redistribute it and/or modify  | 
            ||
| 7 | # it under the terms of the GNU General Public License as published by  | 
            ||
| 8 | # the Free Software Foundation; either version 2 of the License, or  | 
            ||
| 9 | # (at your option) any later version.  | 
            ||
| 10 | #  | 
            ||
| 11 | # Outer Space is distributed in the hope that it will be useful,  | 
            ||
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of  | 
            ||
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the  | 
            ||
| 14 | # GNU General Public License for more details.  | 
            ||
| 15 | #  | 
            ||
| 16 | # You should have received a copy of the GNU General Public License  | 
            ||
| 17 | # along with Outer Space; if not, write to the Free Software  | 
            ||
| 18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA  | 
            ||
| 19 | #  | 
            ||
| 20 | import random, copy, math  | 
            ||
| 21 | |||
| 22 | from ige import log  | 
            ||
| 23 | from ige.ospace import Const  | 
            ||
| 24 | from ige.ospace import Rules  | 
            ||
| 25 | from ige.ospace import Utils  | 
            ||
| 26 | |||
| 27 | import ai_tools as tool  | 
            ||
| 28 | from ais_base import AI  | 
            ||
| 29 | |||
| 30 | class Mutant(AI):  | 
            ||
| 31 | def __init__(self, client):  | 
            ||
| 32 | super(Mutant, self).__init__(client)  | 
            ||
| 33 | tool.doRelevance(self.data, self.client, self.db, 10)  | 
            ||
| 34 | |||
| 35 | def _system_worthiness(self, system, weights):  | 
            ||
| 36 | """ Scans system, and based on planetary composition and weights returns constant.  | 
            ||
| 37 | Weights are expected to be quadruplet of numbers, for [gaia, terrestial, marginal, rest]  | 
            ||
| 38 | """  | 
            ||
| 39 | worth = 0  | 
            ||
| 40 | for planet_id in self.data.myPlanets & set(system.planets):  | 
            ||
| 41 | planet = self.db[planet_id]  | 
            ||
| 42 | if planet.plType == u"I": # gaia  | 
            ||
| 43 | worth += weights[0]  | 
            ||
| 44 | elif planet.plType == u"E": # terrestial  | 
            ||
| 45 | worth += weights[1]  | 
            ||
| 46 | elif planet.plType == u"M": # marginal  | 
            ||
| 47 | worth += weights[2]  | 
            ||
| 48 | else: # junk  | 
            ||
| 49 | worth += weights[3]  | 
            ||
| 50 | return worth  | 
            ||
| 51 | |||
| 52 | def _create_gaia_blueprint(self, space):  | 
            ||
| 53 | # preserve minefield position, and in case there is no  | 
            ||
| 54 | # minefield in the system, try to place it on the first planet  | 
            ||
| 55 | # available  | 
            ||
| 56 | power_plants = math.ceil(max(space - 1, 0) / 6.0)  | 
            ||
| 57 | factories = space - power_plants  | 
            ||
| 58 |         return {Rules.Tech.MUTANTBASE4:1, | 
            ||
| 59 | Rules.Tech.MUTANTPP2:power_plants,  | 
            ||
| 60 | Rules.Tech.MUTANTFACT2:factories}  | 
            ||
| 61 | |||
| 62 | def _create_terrestial_blueprint(self, space):  | 
            ||
| 63 | # preserve minefield position, and in case there is no  | 
            ||
| 64 | # minefield in the system, try to place it on the first planet  | 
            ||
| 65 | # available  | 
            ||
| 66 | power_plants = math.ceil(max(space - 1, 0) / 5.0)  | 
            ||
| 67 | factories = space - power_plants  | 
            ||
| 68 |         return {Rules.Tech.MUTANTBASE3:1, | 
            ||
| 69 | Rules.Tech.MUTANTPP2:power_plants,  | 
            ||
| 70 | Rules.Tech.MUTANTFACT2:factories}  | 
            ||
| 71 | |||
| 72 | def _create_marginal_blueprint(self, space):  | 
            ||
| 73 | # preserve minefield position, and in case there is no  | 
            ||
| 74 | # minefield in the system, try to place it on the first planet  | 
            ||
| 75 | # available  | 
            ||
| 76 | power_plants = math.ceil(max(space - 1, 0) / 7.0)  | 
            ||
| 77 | factories = space - power_plants  | 
            ||
| 78 |         return {Rules.Tech.MUTANTBASE2:1, | 
            ||
| 79 | Rules.Tech.MUTANTPP2:power_plants,  | 
            ||
| 80 | Rules.Tech.MUTANTFACT1:factories}  | 
            ||
| 81 | |||
| 82 | def _create_submarginal_blueprint(self, space):  | 
            ||
| 83 | # preserve minefield position, and in case there is no  | 
            ||
| 84 | # minefield in the system, try to place it on the first planet  | 
            ||
| 85 | # available  | 
            ||
| 86 | power_plants = math.ceil(max(space - 1, 0) / 5.0)  | 
            ||
| 87 | factories = space - power_plants  | 
            ||
| 88 |         return {Rules.Tech.MUTANTBASE:1, | 
            ||
| 89 | Rules.Tech.MUTANTPP1:power_plants,  | 
            ||
| 90 | Rules.Tech.MUTANTFACT1:factories}  | 
            ||
| 91 | |||
| 92 | def _insert_minefield(self, system, system_blueprint):  | 
            ||
| 93 | # pick worst possible planet to put minefield on, don't waste  | 
            ||
| 94 | # precious gaia space if possible  | 
            ||
| 95 | # also ignore actual state - don't be afraid to rebuild if planet is  | 
            ||
| 96 | # promoted  | 
            ||
| 97 | for avoided_types in [(u"I", u"E", u"M"), (u"I", u"E"), (u"I",), ()]:  | 
            ||
| 98 | # sorting, to avoid rebuilds between equivalent planets  | 
            ||
| 99 | for planet_id in sorted(system_blueprint):  | 
            ||
| 100 | planet = self.db[planet_id]  | 
            ||
| 101 | if planet.plType in avoided_types or planet.plSlots == 1:  | 
            ||
| 102 | continue  | 
            ||
| 103 | if Rules.Tech.MUTANTFACT1 in system_blueprint[planet_id]:  | 
            ||
| 104 | assert system_blueprint[planet_id][Rules.Tech.MUTANTFACT1] > 0  | 
            ||
| 105 | system_blueprint[planet_id][Rules.Tech.MUTANTFACT1] -= 1  | 
            ||
| 106 | elif Rules.Tech.MUTANTFACT2 in system_blueprint[planet_id]:  | 
            ||
| 107 | assert system_blueprint[planet_id][Rules.Tech.MUTANTFACT2] > 0  | 
            ||
| 108 | system_blueprint[planet_id][Rules.Tech.MUTANTFACT2] -= 1  | 
            ||
| 109 | else:  | 
            ||
| 110 | continue  | 
            ||
| 111 | system_blueprint[planet_id][Rules.Tech.MUTANTMINES] = 1  | 
            ||
| 112 | return  | 
            ||
| 113 | |||
| 114 | def _cleanup_dict(self, dict_):  | 
            ||
| 115 | for key in dict_.keys():  | 
            ||
| 116 | if not dict_[key]:  | 
            ||
| 117 | del dict_[key]  | 
            ||
| 118 | |||
| 119 | def _create_system_blueprint(self, system):  | 
            ||
| 120 | # create appropriate build plans  | 
            ||
| 121 |         system_blueprint = {} | 
            ||
| 122 | for planet_id in self.data.freePlanets & set(system.planets):  | 
            ||
| 123 |             system_blueprint[planet_id] = {Rules.Tech.MUTANTBASE:1} | 
            ||
| 124 | for planet_id in self.data.myPlanets & set(system.planets):  | 
            ||
| 125 | planet = self.db[planet_id]  | 
            ||
| 126 | space = planet.plSlots - 1 # the main building is there every time  | 
            ||
| 127 | if planet.plType == u"I": # gaia  | 
            ||
| 128 | system_blueprint[planet_id] = self._create_gaia_blueprint(space)  | 
            ||
| 129 | continue  | 
            ||
| 130 | elif planet.plType == u"E": # terrestial  | 
            ||
| 131 | system_blueprint[planet_id] = self._create_terrestial_blueprint(space)  | 
            ||
| 132 | continue  | 
            ||
| 133 | elif planet.plType == u"M": # marginal  | 
            ||
| 134 | system_blueprint[planet_id] = self._create_marginal_blueprint(space)  | 
            ||
| 135 | continue  | 
            ||
| 136 | else: # all sub-marginal types  | 
            ||
| 137 | system_blueprint[planet_id] = self._create_submarginal_blueprint(space)  | 
            ||
| 138 | continue  | 
            ||
| 139 | self._cleanup_dict(system_blueprint)  | 
            ||
| 140 | self._insert_minefield(system, system_blueprint)  | 
            ||
| 141 | return system_blueprint  | 
            ||
| 142 | |||
| 143 | def _system_manager(self):  | 
            ||
| 144 | for planet_id in self.data.myPlanets:  | 
            ||
| 145 | tool.sortStructures(self.client, self.db, planet_id)  | 
            ||
| 146 | for system_id in self.data.mySystems:  | 
            ||
| 147 | system = self.db[system_id]  | 
            ||
| 148 | # creation of final system plans  | 
            ||
| 149 | system_blueprint = self._create_system_blueprint(system)  | 
            ||
| 150 | idle_planets = tool.buildSystem(self.data, self.client, self.db, system_id, self.data.myProdPlanets & set(system.planets), system_blueprint)  | 
            ||
| 151 | # rest of the planets build ships  | 
            ||
| 152 | # first get all our ships in the system  | 
            ||
| 153 |             system_fleet = {} | 
            ||
| 154 | for fleet_id in getattr(system, 'fleets', []):  | 
            ||
| 155 | fleet = self.db[fleet_id]  | 
            ||
| 156 | if getattr(fleet, 'owner', Const.OID_NONE) == self.player.oid:  | 
            ||
| 157 | system_fleet = Utils.dictAddition(system_fleet, tool.getFleetSheet(fleet))  | 
            ||
| 158 | hasSeeders = False  | 
            ||
| 159 | hasSeekers = False  | 
            ||
| 160 | try:  | 
            ||
| 161 | if system_fleet[2] >= 2: hasSeeders = True  | 
            ||
| 162 | except KeyError:  | 
            ||
| 163 | pass  | 
            ||
| 164 | try:  | 
            ||
| 165 | if system_fleet[3] >= 2: hasSeekers = True  | 
            ||
| 166 | except KeyError:  | 
            ||
| 167 | pass  | 
            ||
| 168 | # this variable will gather how valuable system is in regards of fighter defense  | 
            ||
| 169 | # in general, mutant has quite significant planetary defense, so our target is  | 
            ||
| 170 | # to have only about 10 % production spend on support  | 
            ||
| 171 | fighters_to_defend = self._system_worthiness(system, [15,8,5,3])  | 
            ||
| 172 | |||
| 173 | for planet_id in idle_planets:  | 
            ||
| 174 | planet = self.db[planet_id]  | 
            ||
| 175 | shipDraw = random.randint(1, 10)  | 
            ||
| 176 | if (not hasSeeders or not hasSeekers) and shipDraw < 9:  | 
            ||
| 177 | # there is 20% chance it won't build civilian ships, but military one  | 
            ||
| 178 | if not hasSeeders:  | 
            ||
| 179 | planet.prodQueue, self.player.stratRes = self.client.cmdProxy.startConstruction(planet_id, 2, 1, planet_id, True, False, Const.OID_NONE)  | 
            ||
| 180 | continue  | 
            ||
| 181 | elif not hasSeekers:  | 
            ||
| 182 | planet.prodQueue, self.player.stratRes = self.client.cmdProxy.startConstruction(planet_id, 3, 1, planet_id, True, False, Const.OID_NONE)  | 
            ||
| 183 | continue  | 
            ||
| 184 | # rest is creation of ships based on current state + expected guard fighters  | 
            ||
| 185 | try:  | 
            ||
| 186 | fighters = system_fleet[1]  | 
            ||
| 187 | except KeyError:  | 
            ||
| 188 | fighters = 0  | 
            ||
| 189 | try:  | 
            ||
| 190 | bombers = system_fleet[4]  | 
            ||
| 191 | except KeyError:  | 
            ||
| 192 | bombers = 0  | 
            ||
| 193 | expected_fighters = bombers * 1.5 + fighters_to_defend  | 
            ||
| 194 | weight_fighter = 3  | 
            ||
| 195 | weight_bomber = 2  | 
            ||
| 196 | if expected_fighters > fighters:  | 
            ||
| 197 | # we have to build more fighters  | 
            ||
| 198 | weight_fighter += 1  | 
            ||
| 199 | elif expected_fighters < fighters:  | 
            ||
| 200 | # we have too many fighters - let's prefer bombers for now  | 
            ||
| 201 | weight_bomber += 1  | 
            ||
| 202 | choice = Utils.weightedRandom([1,4], [weight_fighter, weight_bomber])  | 
            ||
| 203 | planet.prodQueue, self.player.stratRes = self.client.cmdProxy.startConstruction(planet_id, choice, 2, planet_id, True, False, Const.OID_NONE)  | 
            ||
| 204 | |||
| 205 | def _explore(self, seeker_fleets):  | 
            ||
| 206 | should_repeat = False  | 
            ||
| 207 | for fleet_id in copy.copy(seeker_fleets & self.data.idleFleets):  | 
            ||
| 208 |             max_range = tool.subfleetMaxRange(self.client, self.db, {3:1}, fleet_id) | 
            ||
| 209 | nearest = tool.findNearest(self.db, self.db[fleet_id], self.data.unknownSystems, max_range)  | 
            ||
| 210 | if len(nearest) > 0:  | 
            ||
| 211 | system_id = nearest[0]  | 
            ||
| 212 | # send the fleet  | 
            ||
| 213 | fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,  | 
            ||
| 214 |                     {3:1}, True, fleet_id, Const.FLACTION_MOVE, system_id, None) | 
            ||
| 215 | self.data.myFleetSheets[fleet_id][3] -= 1  | 
            ||
| 216 | if self.data.myFleetSheets[fleet_id][3] == 0:  | 
            ||
| 217 | del self.data.myFleetSheets[fleet_id][3]  | 
            ||
| 218 | seeker_fleets.remove(fleet_id)  | 
            ||
| 219 | else:  | 
            ||
| 220 | should_repeat = True  | 
            ||
| 221 | self.data.unknownSystems.remove(system_id)  | 
            ||
| 222 | return should_repeat  | 
            ||
| 223 | |||
| 224 | View Code Duplication | def _colonize_free_systems(self, seeder_fleets):  | 
            |
| 
                                                                                                    
                        
                         | 
                |||
| 225 | should_repeat = False  | 
            ||
| 226 | for fleet_id in copy.copy(seeder_fleets & self.data.idleFleets):  | 
            ||
| 227 |             max_range = tool.subfleetMaxRange(self.client, self.db, {2:1}, fleet_id) | 
            ||
| 228 | nearest = tool.findNearest(self.db, self.db[fleet_id], self.data.freeSystems, max_range)  | 
            ||
| 229 | if len(nearest) > 0:  | 
            ||
| 230 | system_id = nearest[0]  | 
            ||
| 231 | # finding best planet for deployment  | 
            ||
| 232 | system = self.db[system_id]  | 
            ||
| 233 | max_slots = 0  | 
            ||
| 234 | largest_planet_id = None  | 
            ||
| 235 | for planet_id in system.planets:  | 
            ||
| 236 | planet = self.db[planet_id]  | 
            ||
| 237 | if max_slots < planet.plSlots:  | 
            ||
| 238 | max_slots = planet.plSlots  | 
            ||
| 239 | largest_planet_id = planet_id  | 
            ||
| 240 | # send the fleet  | 
            ||
| 241 | fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,  | 
            ||
| 242 |                     {2:1}, True, fleet_id, Const.FLACTION_DEPLOY, largest_planet_id, 2) | 
            ||
| 243 | self.data.myFleetSheets[fleet_id][2] -= 1  | 
            ||
| 244 | if self.data.myFleetSheets[fleet_id][2] == 0:  | 
            ||
| 245 | del self.data.myFleetSheets[fleet_id][2]  | 
            ||
| 246 | seeder_fleets.remove(fleet_id)  | 
            ||
| 247 | else:  | 
            ||
| 248 | should_repeat = True  | 
            ||
| 249 | self.data.freeSystems.remove(system_id)  | 
            ||
| 250 | return should_repeat  | 
            ||
| 251 | |||
| 252 | View Code Duplication | def _colonize_occupied_systems(self, seeder_fleets):  | 
            |
| 253 | should_repeat = False  | 
            ||
| 254 | for fleet_id in copy.copy(seeder_fleets & self.data.idleFleets):  | 
            ||
| 255 |             max_range = tool.subfleetMaxRange(self.client, self.db, {2:1}, fleet_id) | 
            ||
| 256 | nearest = tool.findNearest(self.db, self.db[fleet_id], self.data.freeSystems, max_range)  | 
            ||
| 257 | fleet = self.db[fleet_id]  | 
            ||
| 258 | orbit_id = fleet.orbiting  | 
            ||
| 259 | if not orbit_id == Const.OID_NONE:  | 
            ||
| 260 | orbit = self.db[orbit_id]  | 
            ||
| 261 | if set(orbit.planets) & self.data.freePlanets and orbit_id in self.data.otherSystems:  | 
            ||
| 262 | max_slots = 0  | 
            ||
| 263 | largest_planet_id = None  | 
            ||
| 264 | for planet_id in set(orbit.planets) & self.data.freePlanets:  | 
            ||
| 265 | planet = self.db[planet_id]  | 
            ||
| 266 | if max_slots < planet.plSlots:  | 
            ||
| 267 | max_slots = planet.plSlots  | 
            ||
| 268 | largest_planet_id = planet_id  | 
            ||
| 269 | # issue the deploy order  | 
            ||
| 270 | fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,  | 
            ||
| 271 |                         {2:1}, True, fleet_id, Const.FLACTION_DEPLOY, largest_planet_id, 2) | 
            ||
| 272 | self.data.myFleetSheets[fleet_id][2] -= 1  | 
            ||
| 273 | if self.data.myFleetSheets[fleet_id][2] == 0:  | 
            ||
| 274 | del self.data.myFleetSheets[fleet_id][2]  | 
            ||
| 275 | seeder_fleets.remove(fleet_id)  | 
            ||
| 276 | return should_repeat  | 
            ||
| 277 | |||
| 278 | def _expansion_manager(self):  | 
            ||
| 279 | should_repeat = True  | 
            ||
| 280 | seeker_fleets = self.data.myFleetsWithDesign.get(3, set())  | 
            ||
| 281 | seeder_fleets = self.data.myFleetsWithDesign.get(2, set())  | 
            ||
| 282 | while should_repeat:  | 
            ||
| 283 | should_repeat = False  | 
            ||
| 284 | should_repeat |= self._explore(seeker_fleets)  | 
            ||
| 285 | should_repeat |= self._colonize_free_systems(seeder_fleets)  | 
            ||
| 286 | should_repeat |= self._colonize_occupied_systems(seeder_fleets)  | 
            ||
| 287 | |||
| 288 | def _ship_design_manager(self):  | 
            ||
| 289 | # there are 4 basic designs created by the server  | 
            ||
| 290 | # 1: Swarmer [Small hull, Cockpit, 2x EMCannon, 2xFTL]  | 
            ||
| 291 | # 2: Seeder [Medium hull, Cockpit, Mutant Colony Pod, 4xFTL]  | 
            ||
| 292 | # 3: Seeker [Small hull, Cockpit, 1x ActiveScan, 2xFTL]  | 
            ||
| 293 | # 4: Sower [Small hull, Cockpit, 1x Conv.Bomb, 2xFTL]  | 
            ||
| 294 | pass  | 
            ||
| 295 | |||
| 296 | def _logistics_manager(self):  | 
            ||
| 297 | for system_id in self.data.mySystems - self.data.myRelevantSystems:  | 
            ||
| 298 | system = self.db[system_id]  | 
            ||
| 299 | for fleet_id in set(system.fleets) & self.data.idleFleets:  | 
            ||
| 300 | fleet = self.db[fleet_id]  | 
            ||
| 301 |                 subfleet = tool.getSubfleet(fleet, {1:0, 4:0}, False) | 
            ||
| 302 | if len(subfleet):  | 
            ||
| 303 |                     fleet_range = tool.subfleetMaxRange(self.client, self.db, {1:0, 4:0}, fleet_id) | 
            ||
| 304 | relevant_sys_id = tool.findNearest(self.db, system, self.data.myRelevantSystems, fleet_range)  | 
            ||
| 305 | if relevant_sys_id:  | 
            ||
| 306 | relevant_sys_id = relevant_sys_id[0]  | 
            ||
| 307 | fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,  | 
            ||
| 308 |                             {1:0, 4:0}, True, fleet_id, Const.FLACTION_MOVE, relevant_sys_id, None) | 
            ||
| 309 | self.data.idleFleets -= set([fleet_id])  | 
            ||
| 310 | else:  | 
            ||
| 311 | min_dist = fleet_range  | 
            ||
| 312 | min_dist_sys_id = None  | 
            ||
| 313 | min_dist_rel = self.data.distanceToRelevance[system_id]  | 
            ||
| 314 | for temp_id, dist in self.data.distanceToRelevance.items():  | 
            ||
| 315 | temp = self.db[temp_id]  | 
            ||
| 316 | distance = math.hypot(temp.x - system.x, temp.y - system.y)  | 
            ||
| 317 | if distance < min_dist and dist < min_dist_rel:  | 
            ||
| 318 | min_dist = distance  | 
            ||
| 319 | min_dist_sys_id = temp_id  | 
            ||
| 320 | min_dist_rel = dist  | 
            ||
| 321 | if min_dist_sys_id:  | 
            ||
| 322 | fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,  | 
            ||
| 323 |                                 {1:0, 4:0}, True, fleet_id, Const.FLACTION_MOVE, min_dist_sys_id, None) | 
            ||
| 324 | self.data.idleFleets -= set([fleet_id])  | 
            ||
| 325 | |||
| 326 | def _get_attack_fleets(self):  | 
            ||
| 327 | attack_fleets = set()  | 
            ||
| 328 | for fleet_id in copy.copy(self.data.myFleets):  | 
            ||
| 329 | fleet = self.db.get(fleet_id, None)  | 
            ||
| 330 | # minimal size of attack fleet is determined by size of originating system - larger  | 
            ||
| 331 | # more developed systems will stage stronger attack fleets  | 
            ||
| 332 | try:  | 
            ||
| 333 | system = self.db[fleet.orbiting]  | 
            ||
| 334 | except KeyError:  | 
            ||
| 335 | # this fleet is not on orbit, set legacy value  | 
            ||
| 336 | minimum = 12  | 
            ||
| 337 | else:  | 
            ||
| 338 | minimum = self._system_worthiness(system, [8,5,3,2]) + 10  | 
            ||
| 339 | if getattr(fleet, 'target', Const.OID_NONE) == Const.OID_NONE and getattr(fleet, 'ships', []):  | 
            ||
| 340 | # this also covers fleets fighting over enemy systems - in that case, there  | 
            ||
| 341 | # is slight chance the fleet will continue to the next system without conquering  | 
            ||
| 342 | # the system first  | 
            ||
| 343 | if fleet.orbiting in self.data.otherSystems and Utils.weightedRandom([True, False], [9,1]):  | 
            ||
| 344 | continue  | 
            ||
| 345 |                 if tool.fleetContains(fleet, {1:minimum, 4:minimum}): | 
            ||
| 346 | attack_fleets.add(fleet_id)  | 
            ||
| 347 | return attack_fleets  | 
            ||
| 348 | |||
| 349 | def _attack_manager(self):  | 
            ||
| 350 | for fleet_id in self._get_attack_fleets():  | 
            ||
| 351 | fleet = self.db[fleet_id]  | 
            ||
| 352 | # send the attack fleet, if in range  | 
            ||
| 353 | sheet = tool.getFleetSheet(fleet)  | 
            ||
| 354 | sowers = sheet[4]  | 
            ||
| 355 | swarmers = min(sheet[1], math.ceil(sowers * 1.5))  | 
            ||
| 356 |             max_range = 0.8 * tool.subfleetMaxRange(self.client, self.db, {1:swarmers, 4:sowers}, fleet_id) | 
            ||
| 357 | # four nearest systems are considered, with probability to be chosen based on order  | 
            ||
| 358 | nearest = tool.findNearest(self.db, fleet, self.data.otherSystems, max_range, 4)  | 
            ||
| 359 | if len(nearest):  | 
            ||
| 360 | # range is adjusted to flatten probabilities a bit  | 
            ||
| 361 | probability_map = map(lambda x: x ** 2, range(6, 2, -1))  | 
            ||
| 362 | target = Utils.weightedRandom(nearest, probability_map)  | 
            ||
| 363 | |||
| 364 | fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,  | 
            ||
| 365 |                     {1:swarmers, 4:sowers}, True, | 
            ||
| 366 | fleet_id, Const.FLACTION_MOVE, target, None)  | 
            ||
| 367 | |||
| 368 | def economy_manager(self):  | 
            ||
| 369 | self._expansion_manager()  | 
            ||
| 370 | self._system_manager()  | 
            ||
| 371 | |||
| 372 | def offense_manager(self):  | 
            ||
| 373 | self._ship_design_manager()  | 
            ||
| 374 | self._logistics_manager()  | 
            ||
| 375 | self._attack_manager()  | 
            ||
| 376 | |||
| 377 | def run(self):  | 
            ||
| 378 | self.economy_manager()  | 
            ||
| 379 | self.offense_manager()  | 
            ||
| 380 | |||
| 381 | def run(aclient):  | 
            ||
| 382 | ai = Mutant(aclient)  | 
            ||
| 383 | ai.run()  | 
            ||
| 384 | aclient.saveDB()  | 
            ||
| 385 | |||
| 386 |