Completed
Push — master ( 78037b...55e295 )
by Marek
16s queued 14s
created

techtree.Grid.print_SDL()   B

Complexity

Conditions 5

Size

Total Lines 42
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 34
nop 1
dl 0
loc 42
rs 8.5973
c 0
b 0
f 0
1
#!/usr/bin/env python2
2
#
3
#  Copyright 2001 - 2018 Ludek Smid [http://www.ospace.net/]
4
#
5
#  This file is part of Outer Space.
6
#
7
#  Outer Space is free software; you can redistribute it and/or modify
8
#  it under the terms of the GNU General Public License as published by
9
#  the Free Software Foundation; either version 2 of the License, or
10
#  (at your option) any later version.
11
#
12
#  Outer Space is distributed in the hope that it will be useful,
13
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
#  GNU General Public License for more details.
16
#
17
#  You should have received a copy of the GNU General Public License
18
#  along with Outer Space; if not, write to the Free Software
19
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
#
21
22
# setup library path
23
import sys
24
sys.path.append("../server/lib")
25
26
# install i18n helpers
27
def N_(text):
28
    return text
29
sys.modules["__builtin__"].N_ = N_
30
31
FONT_TYPE = "Vera.ttf"
32
FONT_SIZE = 13
33
34
# os web style
35
foreground = 0xff, 0xff, 0xff
36
hForeground = 0xff, 0xff, 0xce
37
disablesForeground = 0xff, 0xff, 0x55
38
background = 0x3d, 0x7a, 0x99
39
screenbg = 0x2d, 0x5a, 0x71
40
41
tl2color = {
42
    1: (255, 128, 64),  #orange
43
    2: (255, 255, 128), #yellow
44
    3: (128, 255, 0),   #green
45
    4: (0, 128, 255),   #blue
46
    5: (196, 0, 255),   #purple
47
    6: (196, 0, 0),     #red
48
}
49
50
stratRes = {
51
    0: "None",
52
    1: "Ur",
53
    2: "Ti",
54
    3: "Chrom",
55
    4: "Silic",
56
    5: "Carb",
57
    6: "Anti",
58
    7: "Plut",
59
    8: "Wolf",
60
    100: "Mutagen",
61
    1000: "Unnilseptium"
62
}
63
64
race2Name = {
65
    "B": "Bionic",
66
    "C": "Cyborg",
67
    "H": "Human",
68
    "m": "Mutant",
69
    "r": "Renegade",
70
    "e": "EDEN",
71
    "p": "Pirate",
72
}
73
74
import os
75
from optparse import OptionParser
76
77
# parse command line arguments (needs to be first, so we have configDir ready when importing)
78
parser = OptionParser()
79
parser.add_option("",  "--configdir", dest = "configDir",
80
    metavar = "DIRECTORY", default = os.path.join(os.path.expanduser("~"), ".outerspace"),
81
    help = "Override default configuration directory",)
82
83
options, args = parser.parse_args()
84
85
86
# imports
87
import ige.ospace.Rules as Rules
88
Rules.init(options.configDir)
89
90
import ige.ospace.TechHandlers as TechHandlers
91
import pygame
92
import pygame.locals
93
94
95
class Node(object):
96
    def __init__(self, parent, tech):
97
        self.parent = parent
98
        self.tech = tech
99
        self.children = []
100
        self.final_row = None
101
        self.final_column = None
102
103
    @property
104
    def subtree_row(self):
105
        if self.children:
106
            return sum(map(lambda x: x.subtree_row, self.children))
107
        else:
108
            return 1
109
110
    def _child_row_offset(self, child):
111
        offset = 0
112
        for child_ in self.children:
113
            if child_ == child:
114
                return offset
115
            offset += child_.subtree_row
116
        return offset
117
118
    def row_offset(self, child=None):
119
        offset = self.parent.row_offset(self)
120
121
        if child is None:
122
            return offset
123
        return offset + self._child_row_offset(child)
124
125
    @property
126
    def row(self):
127
        if self.final_row is None:
128
            return self.row_offset()
129
        return self.final_row
130
131
    @property
132
    def column(self):
133
        if self.final_column is None:
134
            return 1 + max(self.parent.column, self.tl_offset_query(self.tech.level) - 1)
135
        return self.final_column
136
137
    def finalize(self):
138
        self.final_column = self.column
139
        self.final_row = self.row
140
141
    def tl_offset(self, tl):
142
        if self.tech.level > tl:
143
            return 0
144
        if self.children:
145
            child_offset = max(map(lambda x: x.tl_offset(tl), self.children))
146
        else:
147
            child_offset = 0
148
        if self.tech.level == tl:
149
            return 1 + child_offset
150
        else:
151
            return child_offset
152
153
    def tl_offset_query(self, tl):
154
        return self.parent.tl_offset_query(tl)
155
156
    def insert(self, tech):
157
        parent_id = tech.researchRequires[0][0]
158
        if self.tech and self.tech.id == parent_id:
159
            for child in self.children:
160
                if tech == child.tech:
161
                    return True
162
            self.children.append(Node(self, tech))
163
            return True
164
        elif not self.children:
165
            return False
166
        for child in self.children:
167
            if child.insert(tech):
168
                return True
169
        return False
170
171
    def __repr__(self):
172
        return str((self.tech.name, self.row, self.column))
173
174
class RootNode(Node):
175
    def __init__(self):
176
        super(RootNode, self).__init__(None, None)
177
178
    @property
179
    def column(self):
180
        return -1
181
182
    def row_offset(self, child=None):
183
        offset = 0
184
        if child is None:
185
            return offset
186
        return offset + self._child_row_offset(child)
187
188
    def tl_offset(self, tl):
189
        if self.children:
190
            return max(map(lambda x: x.tl_offset(tl), self.children))
191
        else:
192
            return 0
193
194
    def tl_offset_query(self, tl):
195
        offset = 0
196
        for i in range(1, tl):
197
            offset += self.tl_offset(i)
198
        return offset
199
200
    def insert(self, tech):
201
        try:
202
            parent_id = tech.researchRequires[0][0]
203
        except IndexError:
204
            self.children.append(Node(self, tech))
205
            return True
206
        super(RootNode, self).insert(tech)
207
208
    def __repr__(self):
209
        return "---ROOT---"
210
211
class Grid(object):
212
    def __init__(self):
213
        self.root = RootNode()
214
215
    def add_node(self, tech):
216
        self.root.insert(tech)
217
218
    def get_max_size(self):
219
        max_column = 0
220
        max_row = 0
221
        for node in self.get_list():
222
            max_column = max(max_column, node.column)
223
            max_row = max(max_row, node.row)
224
        return max_column, max_row
225
226
    def get_list(self):
227
        old_list = None
228
        new_list = self.root.children[:]
229
        while old_list != new_list:
230
            old_list = new_list[:]
231
            for node in old_list:
232
                for child in node.children:
233
                    if child not in new_list:
234
                        new_list.append(child)
235
        return new_list
236
    
237
    def print_ascii(self):
238
        print(len(self.get_list()), self.get_list())
239
240
    def cell_size(self, node):
241
        font = pygame.font.Font(FONT_TYPE, FONT_SIZE)
242
        w, h = font.size(self.get_description(node.tech))
243
        return w + 6 + 12, h + 6
244
245
    def get_description(self, tech):
246
        suffix = ""
247
        if tech.researchReqSRes:
248
            srList = []
249
            for sres in tech.researchReqSRes:
250
                srList.append(stratRes[sres])
251
            suffix += " [%s]" % (", ".join(srList))
252
        if tech.researchRaces != "BCH":
253
            rList = []
254
            for race in tech.researchRaces:
255
                rList.append(race2Name[race])
256
            suffix += " (%s)" % (", ".join(rList))
257
        if tech.finishResearchHandler == TechHandlers.finishResTLAdvance:
258
            suffix += " <TL%d>" % (tech.level + 1)
259
        return "%s%s" % (
260
            tech.name,
261
            suffix,
262
        )
263
264
    def render_node(self, tech, width, height):
265
        surface = pygame.Surface((width, height))
266
        font = pygame.font.Font(FONT_TYPE, FONT_SIZE)
267
        surface.fill(foreground)
268
        surface.fill(background, (1, 1, width - 2, height - 2))
269
        # TL label
270
        color = tl2color[tech.level]
271
        surface.fill(color, (1, 1, 12, height - 2))
272
        font.set_bold(1)
273
        text = font.render(str(tech.level), 1, (0x00, 0x00, 0x00))
274
        surface.blit(text, (2 + (12 - text.get_width()) / 2, (height - text.get_height()) / 2))
275
        # tech name
276
        font.set_bold(0)
277
        if tech.researchDisables:
278
            text = font.render(self.get_description(tech), 1, disablesForeground)
279
        elif tech.researchRaces != "BCH":
280
            text = font.render(self.get_description(tech), 1, hForeground)
281
        else:
282
            text = font.render(self.get_description(tech), 1, foreground)
283
        surface.blit(text, (15, (height - text.get_height()) / 2))
284
        return surface
285
286
    def print_SDL(self):
287
        # grid config
288
        blank_space = 12
289
        padx = 1
290
        pady = 1
291
        font = pygame.font.Font(None, 14)
292
        #
293
        max_column, max_row = self.get_max_size()
294
        # get size of the grid
295
        width = height = 0
296
        for node in self.get_list():
297
            w, h = self.cell_size(node)
298
            width = max(width, w)
299
            height = max(height, h)
300
        node_width = width + 2 * padx + blank_space
301
        node_height = height + 2 * pady
302
        # grid surface
303
        surface = pygame.Surface(((max_column + 1) * node_width, (max_row + 1) * node_height))
304
        surface.fill(screenbg)
305
        # draw tree
306
        for node in self.get_list():
307
            node_surface = self.render_node(node.tech, width, height)
308
            surface.blit(node_surface, (node.column * node_width, node.row * node_height))
309
            parent = node.parent
310
            if parent.tech is None:
311
                continue
312
            pygame.draw.lines(surface, foreground, 0,
313
                (
314
                    (parent.column * node_width + width, parent.row * node_height + node_height / 2),
315
                    (parent.column * node_width + width + blank_space / 2, parent.row * node_height + node_height / 2),
316
                    (parent.column * node_width + width + blank_space / 2, node.row * node_height + node_height / 2),
317
                    (node.column * node_width, node.row * node_height + node_height / 2),
318
                ),
319
            )
320
            # improvement requiremt
321
            try:
322
                impr = node.tech.researchRequires[0][1]
323
                text = font.render(str(impr), 1, foreground)
324
                surface.blit(text, (node.column * node_width - text.get_width() - 1, node.row * node_height))
325
            except IndexError:
326
                continue
327
        return surface
328
329
def useful_tech(tech, races):
330
    return "#" not in tech.researchRaces and tech.level <= 6 and set(races) & set(tech.researchRaces)
331
332
def add_techs(grid, techs):
333
    old_size = 0
334
    new_size = len(grid.get_list())
335
    while old_size != new_size:
336
        old_size = new_size
337
        for tech in techs[:]:
338
            if not tech.researchRequires:
339
                techs.remove(tech)
340
                continue
341
            if tech.researchRequires:
342
                if grid.add_node(tech):
343
                    techs.remove(tech)
344
        new_size = len(grid.get_list())
345
346
def techtree(filename, races):
347
    # process tree
348
    grid = Grid()
349
    techs = [tech for tech in list(Rules.techs.itervalues()) if useful_tech(tech, races)]
350
    techs = sorted(techs, key=lambda x: x.level)
351
    # adding roots
352
    for tech in techs:
353
        if not tech.researchRequires:
354
            grid.add_node(tech)
355
356
    # adding the rest (by tech levels, so visually, it looks sorted (cascading effect))
357
    for tl in range(1, 7):
358
        tl_techs = [tech for tech in techs if tech.level == tl]
359
        add_techs(grid, tl_techs)
360
361
    grid.root.finalize()
362
    for node in grid.get_list():
363
        node.finalize()
364
365
    pygame.image.save(grid.print_SDL(), filename + ".png")
366
367
pygame.init()
368
techtree("techtree_all", "BCH")
369
techtree("techtree_bionic", "B")
370
techtree("techtree_cyborg", "C")
371
techtree("techtree_human", "H")
372