Passed
Pull Request — master (#185)
by Marek
01:47
created

ige.BookingMngr   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 174
dl 0
loc 243
rs 6
c 0
b 0
f 0
wmc 55

18 Methods

Rating   Name   Duplication   Size   Complexity  
A BookingMngr.shutdown() 0 3 1
A BookingMngr.checkpoint() 0 2 1
A Booking.__init__() 0 4 1
B BookingMngr._get_challenges() 0 13 7
A Booking.is_filled() 0 2 1
A Booking.toggle_booking() 0 6 2
A Booking.discard_booking() 0 2 1
B BookingMngr.init_bookings() 0 13 6
A BookingMngr.clear() 0 2 1
A BookingMngr.__init__() 0 7 1
A Booking.answer() 0 6 1
A BookingMngr.upgrade() 0 2 1
A BookingMngr.backup() 0 2 1
A BookingMngr.trigger_galaxy_start() 0 23 3
C BookingMngr.get_booking_offers() 0 27 7
F BookingMngr._find_galaxy_name() 0 46 13
A BookingMngr.toggle_booking() 0 17 4
A BookingMngr.get_booking_answers() 0 10 3

How to fix   Complexity   

Complexity

Complex classes like ige.BookingMngr 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
21
import os
22
import time
23
24
import data
25
import ige
26
import log
27
28
from ige import GameException
29
from ige.Transaction import Transaction
30
from ige.IDataHolder import IDataHolder
31
from ige.ospace.GalaxyGenerator import GalaxyGenerator
32
import ige.ospace.Const as Const
33
34
class BookingMngrException(Exception):
35
    pass
36
37
class Booking(object):
38
    def __init__(self):
39
        self.booked_players = set([])
40
        self.last_creation = None
41
        self.capacity = None
42
43
    def toggle_booking(self, player, threshold):
44
        try:
45
            self.booked_players.remove(player)
46
        except KeyError:
47
            self.booked_players.add(player)
48
        return self.is_filled(threshold)
49
50
    def discard_booking(self, player):
51
        self.booked_players.discard(player)
52
53
    def is_filled(self, threshold):
54
        return len(self.booked_players) >= self.capacity * min(1.0, threshold)
55
56
    def answer(self, player):
57
        answer = IDataHolder()
58
        answer.bookings = len(self.booked_players)
59
        answer.last_creation = self.last_creation
60
        answer.is_booked = player in self.booked_players
61
        return answer
62
63
class BookingMngr(object):
64
    def __init__(self, clientMngr, gameMngr, database, threshold):
65
        self.clientMngr = clientMngr
66
        self.gameMngr = gameMngr
67
        self.database = database
68
        self.threshold = threshold
69
        self.offerings = GalaxyGenerator().templates
70
        self.init_bookings()
71
72
    def init_bookings(self):
73
        for gal_type in self.offerings:
74
            if not self.database.has_key(gal_type):
75
                self.database.create(Booking(), gal_type)
76
            if self.offerings[gal_type].scenario == Const.SCENARIO_SINGLE:
77
                # hardcoded 1, as some galaxies has rebels in them
78
                self.database[gal_type].capacity = 1
79
            else:
80
                self.database[gal_type].capacity = self.offerings[gal_type].players
81
        # cleanup of those not used anymore
82
        for gal_type in self.database.keys():
83
            if gal_type not in self.offerings:
84
                del self.database[gal_type]
85
86
    def shutdown(self):
87
        log.message('Shutdown')
88
        self.database.shutdown()
89
90
    def checkpoint(self):
91
        self.database.checkpoint()
92
93
    def clear(self):
94
        self.database.clear()
95
96
    def backup(self, basename):
97
        self.database.backup(basename)
98
99
    def upgrade(self):
100
        return
101
102
    def _get_challenges(self, template):
103
        challenges = []
104
        if template.scenario == Const.SCENARIO_SINGLE and template.players > 1:
105
            challenges.append(Const.T_AIPLAYER)
106
        if set([Const.SR_TL1A, Const.SR_TL1B]).intersection(template.resources):
107
            challenges.append(Const.T_AIRENPLAYER)
108
        if set([Const.DISEASE_MUTANT]).intersection(template.diseases):
109
            challenges.append(Const.T_AIMUTPLAYER)
110
        if set([Const.SR_TL3A, Const.SR_TL3B, Const.SR_TL3C]).intersection(template.resources):
111
            challenges.append(Const.T_AIPIRPLAYER)
112
        if set([Const.SR_TL5A, Const.SR_TL5B, Const.SR_TL5C]).intersection(template.resources):
113
            challenges.append(Const.T_AIEDENPLAYER)
114
        return challenges
115
116
    def get_booking_offers(self, sid):
117
        hideSingle = self.gameMngr.singleGamesLimit(sid)
118
        answers = {}
119
        # TODO: filter single player for users with limit
120
        for gal_type, template in self.offerings.iteritems():
121
            if gal_type == "Test":
122
                continue
123
            if hideSingle and template.scenario == Const.SCENARIO_SINGLE:
124
                continue
125
            answer = IDataHolder()
126
            answer.scenario = template.scenario
127
            answer.minPlanets = template.minPlanets
128
            answer.maxPlanets = template.maxPlanets
129
            answer.radius = template.radius
130
            answer.players = template.players
131
            answer.resources = template.resources.keys()
132
            answer.challenges = self._get_challenges(template)
133
134
            if not template.startR[0] or not template.startR[1]:
135
                # that means grouping is used to maximize distance between players
136
                # most likely brawl scenario
137
                answer.playerGroup = 1
138
            else:
139
                answer.playerGroup = template.playerGroup
140
141
            answers[gal_type] = answer
142
        return answers, None
143
144
    def get_booking_answers(self, sid):
145
        offers = self.get_booking_offers(sid)[0].keys()
146
        player = self.clientMngr.getSession(sid).login
147
        answers = {}
148
        for key in self.database.keys():
149
            if key not in offers:
150
                # most likely single player limit reached
151
                continue
152
            answers[key] = self.database[key].answer(player)
153
        return answers, None
154
155
    def toggle_booking(self, sid, gal_type):
156
        # we have to check single player limit
157
        scenario = self.offerings[gal_type].scenario
158
        if scenario == Const.SCENARIO_SINGLE and self.gameMngr.singleGamesLimit(sid):
159
            # limit reached, no toggling allowed
160
            raise GameException('Limit of single player galaxies reached.')
161
162
        player = self.clientMngr.getSession(sid).login
163
        log.debug("Player '{0}' toggled booking of '{1}'".format(player, gal_type))
164
        triggered = False
165
        if self.database[gal_type].toggle_booking(player, self.threshold):
166
            self.trigger_galaxy_start(gal_type)
167
            triggered = True
168
        # get_booking_answers returns tuple, we only need first part
169
        answers = self.get_booking_answers(sid)[0]
170
        answers[None] = triggered
171
        return answers, None
172
173
    def trigger_galaxy_start(self, gal_type):
174
        log.debug("Triggering new subscribed galaxy '{0}'".format(gal_type))
175
        # triggers galaxy start regardless booking numbers
176
        booking = self.database[gal_type]
177
178
        players = list(booking.booked_players)
179
180
        # TODO: I don't know what I am doing, this seems to be a terrible hack
181
        universe = self.gameMngr.db[ige.Const.OID_UNIVERSE]
182
        tran = Transaction(self.gameMngr, ige.Const.OID_ADMIN)
183
        name = self._find_galaxy_name(gal_type, players)
184
        newGalaxyID = self.gameMngr.cmdPool[universe.type].createNewSubscribedGalaxy(tran, universe, name, gal_type, players)
185
        # update booking data
186
        booking.booked_players = set([])
187
        booking.last_creation = time.time()
188
189
        # for now, we need to remove bookings of player from other queues
190
        # TODO: remove after decoupling account and player objects
191
        for player in players:
192
            log.debug("Removing other bookings of player '{0}'".format(player))
193
            for key in self.database.keys():
194
                self.database[key].discard_booking(player)
195
        return newGalaxyID, None
196
197
    def _find_galaxy_name(self, gal_type, players):
198
        scenario = self.offerings[gal_type].scenario
199
        universe = self.gameMngr.db[ige.Const.OID_UNIVERSE]
200
        names_in_use = set([])
201
        # fetch list of used galaxy names
202
        for galaxy_id in universe.galaxies:
203
            galaxy = self.gameMngr.db[galaxy_id]
204
            names_in_use.add(galaxy.name)
205
206
        if scenario == Const.SCENARIO_SINGLE:
207
            player_name = players[0]
208
            name = "{0}-{1}-{2}".format(gal_type, player_name, universe.turn)
209
            if name not in names_in_use:
210
                return name
211
            iterator = 1
212
            while True:
213
                name = "{0}-{1}-{2}.{3}".format(gal_type, player_name, universe.turn, iterator)
214
                if name in names_in_use:
215
                    iterator += 1
216
                    continue
217
                # found new name!
218
                return name
219
        elif scenario in [Const.SCENARIO_COOP, Const.SCENARIO_BRAWL]:
220
            name = "{0}-{1}".format(gal_type, universe.turn)
221
            if name not in names_in_use:
222
                return name
223
            iterator = 1
224
            while True:
225
                name = "{0}-{1}.{2}".format(gal_type, universe.turn, iterator)
226
                if name in names_in_use:
227
                    iterator += 1
228
                    continue
229
                # found new name!
230
                return name
231
        # Outer Space has privilege of having traditional galaxy names
232
        log.debug("Searching for available galaxy name")
233
        all_names = []
234
        for name in open(data.GALAXY_NAMES_FILE):
235
            all_names.append(name.strip())
236
237
        for name in all_names:
238
            if name in names_in_use:
239
                continue
240
            return name
241
        # no name available
242
        return None
243
244