AIs.ais_mutant.Mutant._system_manager()   F
last analyzed

Complexity

Conditions 19

Size

Total Lines 61
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 50
nop 1
dl 0
loc 61
rs 0.5999
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like AIs.ais_mutant.Mutant._system_manager() 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 ai import AI
29
30
class Mutant(AI):
31
    def __init__(self, client):
32
        enemy_types = [Const.T_PLAYER, Const.T_AIPLAYER, Const.T_PIRPLAYER, Const.T_AIPIRPLAYER, Const.T_AIRENPLAYER]
33
        super(Mutant, self).__init__(client, enemy_types)
34
        tool.doRelevance(self.data, self.client, self.db, 10)
35
36
    def _system_worthiness(self, system, weights):
37
        """ Scans system, and based on planetary composition and weights returns constant.
38
        Weights are expected to be quadruplet of numbers, for [gaia, terrestial, marginal, rest]
39
        """
40
        worth = 0
41
        for planet_id in self.data.myPlanets & set(system.planets):
42
            planet = self.db[planet_id]
43
            if planet.plType == u"I":  # gaia
44
                worth += weights[0]
45
            elif planet.plType == u"E":  # terrestial
46
                worth += weights[1]
47
            elif planet.plType == u"M":  # marginal
48
                worth += weights[2]
49
            else:  # junk
50
                worth += weights[3]
51
        return worth
52
53
    def _create_gaia_blueprint(self, space):
54
        # preserve minefield position, and in case there is no
55
        # minefield in the system, try to place it on the first planet
56
        # available
57
        power_plants = math.ceil(max(space - 1, 0) / 6.0)
58
        factories = space - power_plants
59
        return {Rules.Tech.MUTANTBASE4:1,
60
                Rules.Tech.MUTANTPP2:power_plants,
61
                Rules.Tech.MUTANTFACT2:factories}
62
63
    def _create_terrestial_blueprint(self, space):
64
        # preserve minefield position, and in case there is no
65
        # minefield in the system, try to place it on the first planet
66
        # available
67
        power_plants = math.ceil(max(space - 1, 0) / 5.0)
68
        factories = space - power_plants
69
        return {Rules.Tech.MUTANTBASE3:1,
70
                Rules.Tech.MUTANTPP2:power_plants,
71
                Rules.Tech.MUTANTFACT2:factories}
72
73
    def _create_marginal_blueprint(self, space):
74
        # preserve minefield position, and in case there is no
75
        # minefield in the system, try to place it on the first planet
76
        # available
77
        power_plants = math.ceil(max(space - 1, 0) / 7.0)
78
        factories = space - power_plants
79
        return {Rules.Tech.MUTANTBASE2:1,
80
                Rules.Tech.MUTANTPP2:power_plants,
81
                Rules.Tech.MUTANTFACT1:factories}
82
83
    def _create_submarginal_blueprint(self, space):
84
        # preserve minefield position, and in case there is no
85
        # minefield in the system, try to place it on the first planet
86
        # available
87
        power_plants = math.ceil(max(space - 1, 0) / 5.0)
88
        factories = space - power_plants
89
        return {Rules.Tech.MUTANTBASE:1,
90
                Rules.Tech.MUTANTPP1:power_plants,
91
                Rules.Tech.MUTANTFACT1:factories}
92
93
    def _insert_minefield(self, system, system_blueprint):
94
        # pick worst possible planet to put minefield on, don't waste
95
        # precious gaia space if possible
96
        # also ignore actual state - don't be afraid to rebuild if planet is
97
        # promoted
98
        for avoided_types in [(u"I", u"E", u"M"), (u"I", u"E"), (u"I",), ()]:
99
            # sorting, to avoid rebuilds between equivalent planets
100
            for planet_id in sorted(system_blueprint):
101
                planet = self.db[planet_id]
102
                if planet.plType in avoided_types or planet.plSlots == 1:
103
                    continue
104
                if Rules.Tech.MUTANTFACT1 in system_blueprint[planet_id]:
105
                    assert system_blueprint[planet_id][Rules.Tech.MUTANTFACT1] > 0
106
                    system_blueprint[planet_id][Rules.Tech.MUTANTFACT1] -= 1
107
                elif Rules.Tech.MUTANTFACT2 in system_blueprint[planet_id]:
108
                    assert system_blueprint[planet_id][Rules.Tech.MUTANTFACT2] > 0
109
                    system_blueprint[planet_id][Rules.Tech.MUTANTFACT2] -= 1
110
                else:
111
                    continue
112
                system_blueprint[planet_id][Rules.Tech.MUTANTMINES] = 1
113
                return
114
115
    def _cleanup_dict(self, dict_):
116
        for key in dict_.keys():
117
            if not dict_[key]:
118
                del dict_[key]
119
120
    def _create_system_blueprint(self, system):
121
        # create appropriate build plans
122
        system_blueprint = {}
123
        for planet_id in self.data.freePlanets & set(system.planets):
124
            system_blueprint[planet_id] = {Rules.Tech.MUTANTBASE:1}
125
        for planet_id in self.data.myPlanets & set(system.planets):
126
            planet = self.db[planet_id]
127
            space = planet.plSlots - 1 # the main building is there every time
128
            if planet.plType == u"I":  # gaia
129
                system_blueprint[planet_id] = self._create_gaia_blueprint(space)
130
                continue
131
            elif planet.plType == u"E":  # terrestial
132
                system_blueprint[planet_id] = self._create_terrestial_blueprint(space)
133
                continue
134
            elif planet.plType == u"M":  # marginal
135
                system_blueprint[planet_id] = self._create_marginal_blueprint(space)
136
                continue
137
            else: # all sub-marginal types
138
                system_blueprint[planet_id] = self._create_submarginal_blueprint(space)
139
                continue
140
        self._cleanup_dict(system_blueprint)
141
        self._insert_minefield(system, system_blueprint)
142
        return system_blueprint
143
144
    def _system_manager(self):
145
        for planet_id in self.data.myPlanets:
146
            tool.sortStructures(self.client, self.db, planet_id)
147
        for system_id in self.data.mySystems:
148
            system = self.db[system_id]
149
            # creation of final system plans
150
            system_blueprint = self._create_system_blueprint(system)
151
            idle_planets = tool.buildSystem(self.data, self.client, self.db, system_id, self.data.myProdPlanets & set(system.planets), system_blueprint)
152
            # rest of the planets build ships
153
            # first get all our ships in the system
154
            system_fleet = {}
155
            for fleet_id in getattr(system, 'fleets', []):
156
                fleet = self.db[fleet_id]
157
                if getattr(fleet, 'owner', Const.OID_NONE) == self.player.oid:
158
                    system_fleet = Utils.dictAddition(system_fleet, tool.getFleetSheet(fleet))
159
            hasSeeders = False
160
            hasSeekers = False
161
            try:
162
                if system_fleet[2] >= 2: hasSeeders = True
163
            except KeyError:
164
                pass
165
            try:
166
                if system_fleet[3] >= 2: hasSeekers = True
167
            except KeyError:
168
                pass
169
            # this variable will gather how valuable system is in regards of fighter defense
170
            # in general, mutant has quite significant planetary defense, so our target is
171
            # to have only about 10 % production spend on support
172
            fighters_to_defend = self._system_worthiness(system, [15,8,5,3])
173
174
            for planet_id in idle_planets:
175
                planet = self.db[planet_id]
176
                shipDraw = random.randint(1, 10)
177
                if (not hasSeeders or not hasSeekers) and shipDraw < 9:
178
                    # there is 20% chance it won't build civilian ships, but military one
179
                    if not hasSeeders:
180
                        planet.prodQueue, self.player.stratRes = self.client.cmdProxy.startConstruction(planet_id, 2, 1, planet_id, True, False, Const.OID_NONE)
181
                        continue
182
                    elif not hasSeekers:
183
                        planet.prodQueue, self.player.stratRes = self.client.cmdProxy.startConstruction(planet_id, 3, 1, planet_id, True, False, Const.OID_NONE)
184
                        continue
185
                # rest is creation of ships based on current state + expected guard fighters
186
                try:
187
                    fighters = system_fleet[1]
188
                except KeyError:
189
                    fighters = 0
190
                try:
191
                    bombers = system_fleet[4]
192
                except KeyError:
193
                    bombers = 0
194
                expected_fighters = bombers * 1.5 + fighters_to_defend
195
                weight_fighter = 3
196
                weight_bomber = 2
197
                if expected_fighters > fighters:
198
                    # we have to build more fighters
199
                    weight_fighter += 1
200
                elif expected_fighters < fighters:
201
                    # we have too many fighters - let's prefer bombers for now
202
                    weight_bomber += 1
203
                choice = Utils.weightedRandom([1,4], [weight_fighter, weight_bomber])
204
                planet.prodQueue, self.player.stratRes = self.client.cmdProxy.startConstruction(planet_id, choice, 2, planet_id, True, False, Const.OID_NONE)
205
206
    def _colonize_occupied_systems(self, seeder_id):
207
        seeder_fleets = self.data.myFleetsWithDesign.get(seeder_id, set())
208
        should_repeat = False
209
        for fleet_id in copy.copy(seeder_fleets & self.data.idleFleets):
210
            fleet = self.db[fleet_id]
211
            orbit_id = fleet.orbiting
212
            if orbit_id != Const.OID_NONE:
213
                orbit = self.db[orbit_id]
214
                if set(orbit.planets) & self.data.freePlanets and orbit_id in self.data.otherSystems:
215
                    target_id = self._find_best_planet(set(orbit.planets) & self.data.freePlanets)
216
                    fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,
217
                        {seeder_id:1}, True, fleet_id, Const.FLACTION_DEPLOY, target_id, seeder_id)
218
                    self.data.myFleetSheets[fleet_id][seeder_id] -= 1
219
                    if self.data.myFleetSheets[fleet_id][seeder_id] == 0:
220
                        del self.data.myFleetSheets[fleet_id][seeder_id]
221
                        seeder_fleets.remove(fleet_id)
222
        return should_repeat
223
224
    def _expansion_manager(self):
225
        should_repeat = True
226
        seeker_id = 3
227
        seeder_id = 2
228
        while should_repeat:
229
            should_repeat = False
230
            should_repeat |= self._explore(seeker_id)
231
            should_repeat |= self._colonize_free_systems(copy.copy(self.data.freeSystems), seeder_id)
232
            should_repeat |= self._colonize_occupied_systems(seeder_id)
233
234
    def _ship_design_manager(self):
235
        # there are 4 basic designs    created by the server
236
        # 1: Swarmer [Small hull, Cockpit, 2x EMCannon, 2xFTL]
237
        # 2: Seeder [Medium hull, Cockpit, Mutant Colony Pod, 4xFTL]
238
        # 3: Seeker [Small hull, Cockpit, 1x ActiveScan, 2xFTL]
239
        # 4: Sower [Small hull, Cockpit, 1x Conv.Bomb, 2xFTL]
240
        pass
241
242
    def _logistics_manager(self):
243
        for system_id in self.data.mySystems - self.data.myRelevantSystems:
244
            system = self.db[system_id]
245
            for fleet_id in set(system.fleets) & self.data.idleFleets:
246
                fleet = self.db[fleet_id]
247
                subfleet = tool.getSubfleet(fleet, {1:0, 4:0}, False)
248
                if len(subfleet):
249
                    fleet_range = tool.subfleetMaxRange(self.client, self.db, {1:0, 4:0}, fleet_id)
250
                    relevant_sys_id = tool.findNearest(self.db, system, self.data.myRelevantSystems, fleet_range)
251
                    if relevant_sys_id:
252
                        relevant_sys_id = relevant_sys_id[0]
253
                        fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,
254
                            {1:0, 4:0}, True, fleet_id, Const.FLACTION_MOVE, relevant_sys_id, None)
255
                        self.data.idleFleets -= set([fleet_id])
256
                    else:
257
                        min_dist = fleet_range
258
                        min_dist_sys_id = None
259
                        min_dist_rel = self.data.distanceToRelevance[system_id]
260
                        for temp_id, dist in self.data.distanceToRelevance.items():
261
                            temp = self.db[temp_id]
262
                            distance = math.hypot(temp.x - system.x, temp.y - system.y)
263
                            if distance < min_dist and dist < min_dist_rel:
264
                                min_dist = distance
265
                                min_dist_sys_id = temp_id
266
                                min_dist_rel = dist
267
                        if min_dist_sys_id:
268
                            fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,
269
                                {1:0, 4:0}, True, fleet_id, Const.FLACTION_MOVE, min_dist_sys_id, None)
270
                            self.data.idleFleets -= set([fleet_id])
271
272
    def _get_attack_fleets(self):
273
        attack_fleets = set()
274
        for fleet_id in copy.copy(self.data.myFleets):
275
            fleet = self.db.get(fleet_id, None)
276
            # minimal size of attack fleet is determined by size of originating system - larger
277
            # more developed systems will stage stronger attack fleets
278
            try:
279
                system = self.db[fleet.orbiting]
280
            except KeyError:
281
                # this fleet is not on orbit, set legacy value
282
                minimum = 12
283
            else:
284
                minimum = self._system_worthiness(system, [8,5,3,2]) + 10
285
            if getattr(fleet, 'target', Const.OID_NONE) == Const.OID_NONE and getattr(fleet, 'ships', []):
286
                # this also covers fleets fighting over enemy systems - in that case, there
287
                # is slight chance the fleet will continue to the next system without conquering
288
                # the system first
289
                if fleet.orbiting in self.data.enemySystems and Utils.weightedRandom([True, False], [9,1]):
290
                    continue
291
                if tool.fleetContains(fleet, {1:minimum, 4:minimum}):
292
                    attack_fleets.add(fleet_id)
293
        return attack_fleets
294
295
    def _attack_manager(self):
296
        for fleet_id in self._get_attack_fleets():
297
            fleet = self.db[fleet_id]
298
            # send the attack fleet, if in range
299
            sheet = tool.getFleetSheet(fleet)
300
            sowers = sheet[4]
301
            swarmers = min(sheet[1], math.ceil(sowers * 1.5))
302
            max_range = 0.8 * tool.subfleetMaxRange(self.client, self.db, {1:swarmers, 4:sowers}, fleet_id)
303
            # four nearest systems are considered, with probability to be chosen based on order
304
            nearest = tool.findNearest(self.db, fleet, self.data.enemySystems, max_range, 4)
305
            if len(nearest):
306
                # range is adjusted to flatten probabilities a bit
307
                probability_map = map(lambda x: x ** 2, range(2 + len(nearest), 2, -1))
308
                target = Utils.weightedRandom(nearest, probability_map)
309
310
                fleet, new_fleet, my_fleets = tool.orderPartFleet(self.client, self.db,
311
                    {1:swarmers, 4:sowers}, True,
312
                    fleet_id, Const.FLACTION_MOVE, target, None)
313
314
    def economy_manager(self):
315
        self._expansion_manager()
316
        self._system_manager()
317
318
    def offense_manager(self):
319
        self._ship_design_manager()
320
        self._logistics_manager()
321
        self._attack_manager()
322
323
    def run(self):
324
        self.diplomacy_manager(friendly_types=[Const.T_AIMUTPLAYER],
325
                               pacts=[Const.PACT_ALLOW_CIVILIAN_SHIPS, Const.PACT_ALLOW_MILITARY_SHIPS,
326
                                      Const.PACT_ALLOW_TANKING, Const.PACT_SHARE_SCANNER,
327
                                      Const.PACT_MINOR_SCI_COOP, Const.PACT_MAJOR_SCI_COOP,
328
                                      Const.PACT_MINOR_CP_COOP, Const.PACT_MAJOR_CP_COOP])
329
        top_prio_tech = [Rules.Tech.MUTANTBASE,
330
                         Rules.Tech.MUTANTBASE2,
331
                         Rules.Tech.MUTANTBASE3,
332
                         Rules.Tech.MUTANTBASE4,
333
                         Rules.Tech.MUTANTPP1,
334
                         Rules.Tech.MUTANTPP2,
335
                         Rules.Tech.MUTANTFACT1,
336
                         Rules.Tech.MUTANTFACT2,
337
                         Rules.Tech.MUTANTMINES,
338
                         Rules.Tech.FTLENG1,
339
                         ]
340
        mid_prio_tech = [Rules.Tech.SMALLHULL1,
341
                         Rules.Tech.SCOCKPIT1,
342
                         Rules.Tech.SCANNERMOD1,
343
                         Rules.Tech.CONBOMB1,
344
                         Rules.Tech.EMCANNON,
345
                         Rules.Tech.TORPEDO,
346
                         Rules.Tech.SSROCKET2,
347
                         Rules.Tech.STLENG1,
348
                         Rules.Tech.MUTANTPOD,
349
                         ]
350
        low_prio_tech = [Rules.Tech.CANNON1,
351
                         Rules.Tech.SSROCKET,
352
                         ]
353
        tech_prio = {10: top_prio_tech,
354
                     5: mid_prio_tech,
355
                     1: low_prio_tech,
356
                     }
357
        self.research_manager(tech_prio)
358
        self.economy_manager()
359
        self.offense_manager()
360
361
def run(aclient):
362
    ai = Mutant(aclient)
363
    ai.run()
364
    aclient.saveDB()
365
366