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 bisect |
22
|
|
|
import glob |
23
|
|
|
import math |
24
|
|
|
import os |
25
|
|
|
import re |
26
|
|
|
|
27
|
|
|
import gdata, client |
28
|
|
|
import pygame, pygame.image |
29
|
|
|
from osci import gdata |
30
|
|
|
import ige.ospace.Const as Const |
31
|
|
|
from ige.ospace import Rules |
32
|
|
|
from ige import log |
33
|
|
|
|
34
|
|
|
import resources |
35
|
|
|
|
36
|
|
|
whiteShift = 80000 |
37
|
|
|
redShift = 90000 |
38
|
|
|
|
39
|
|
|
smallStarImgs = None |
40
|
|
|
techImgs = None |
41
|
|
|
bigStarImgs = None |
42
|
|
|
planetImgs = None |
43
|
|
|
planetImgCnt = None |
44
|
|
|
buttonImgs = None |
45
|
|
|
cmdInProgressImg = None |
46
|
|
|
loginLogoImg = None |
47
|
|
|
structProblemImg = None |
48
|
|
|
structOffImg = None |
49
|
|
|
icons = {} |
50
|
|
|
ui_icons = {} |
51
|
|
|
|
52
|
|
|
def initialize(): |
53
|
|
|
# needed for progress dlg |
54
|
|
|
global loginLogoImg |
55
|
|
|
loginLogoImg = pygame.image.load(resources.get('logo-login.png')).convert_alpha() |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
def updateProgress(curr, progress_dlg): |
59
|
|
|
if not progress_dlg: return |
60
|
|
|
if curr % 30 == 0: |
61
|
|
|
periods = '.' * (curr / 30 % 4) |
62
|
|
|
progress_dlg.setProgress(_('Loading resources' + periods), curr) |
63
|
|
|
|
64
|
|
|
|
65
|
|
|
def loadResources(progress_dlg=None): |
66
|
|
|
curr = 0 |
67
|
|
|
max = len(glob.glob(resources.get('galaxy/*.png'))) \ |
68
|
|
|
+ len(glob.glob(resources.get('techs/*.png'))) \ |
69
|
|
|
+ len(glob.glob(resources.get('system/*.png'))) \ |
70
|
|
|
+ len(glob.glob(resources.get('icons/*.png'))) \ |
71
|
|
|
+ len(glob.glob(resources.get('buttons/*.png'))) |
72
|
|
|
if progress_dlg: |
73
|
|
|
progress_dlg.display(_('Loading resources'), 0, max) |
74
|
|
|
# load star imgs |
75
|
|
|
global smallStarImgs |
76
|
|
|
smallStarImgs = {} |
77
|
|
|
for filename in glob.glob(resources.get('galaxy/star_*.png')): |
78
|
|
|
name = re.search("star_([^.]+).png", filename).group(1) |
79
|
|
|
smallStarImgs[name] = pygame.image.load(filename).convert_alpha() |
80
|
|
|
curr += 1 |
81
|
|
|
updateProgress(curr, progress_dlg) |
82
|
|
|
# load tech imgs |
83
|
|
|
global techImgs |
84
|
|
|
techImgs = {} |
85
|
|
|
white = pygame.Surface((37, 37)) |
86
|
|
|
white.fill((255, 255, 255)) |
87
|
|
|
white.set_alpha(64) |
88
|
|
|
red = pygame.Surface((37, 37)) |
89
|
|
|
red.fill((255, 0, 0)) |
90
|
|
|
red.set_alpha(64) |
91
|
|
|
for filename in glob.glob(resources.get('techs/????.png')): |
92
|
|
|
name = os.path.splitext(os.path.basename(filename))[0] |
93
|
|
|
imgID = int(name) |
|
|
|
|
94
|
|
|
techImgs[imgID] = pygame.image.load(filename).convert_alpha() |
95
|
|
|
copyImg = techImgs[imgID].convert_alpha() |
96
|
|
|
copyImg.blit(white, (0, 0)) |
97
|
|
|
techImgs[imgID + whiteShift] = copyImg |
98
|
|
|
copyImg = techImgs[imgID].convert_alpha() |
99
|
|
|
copyImg.blit(red, (0, 0)) |
100
|
|
|
techImgs[imgID + redShift] = copyImg |
101
|
|
|
curr += 1 |
102
|
|
|
updateProgress(curr, progress_dlg) |
103
|
|
|
# load big star imgs |
104
|
|
|
global bigStarImgs |
105
|
|
|
bigStarImgs = {} |
106
|
|
|
for filename in glob.glob(resources.get('system/star_*.png')): |
107
|
|
|
name = re.search("star_([^.]+).png", filename).group(1) |
108
|
|
|
bigStarImgs[name] = pygame.image.load(filename).convert_alpha() |
109
|
|
|
curr += 1 |
110
|
|
|
updateProgress(curr, progress_dlg) |
111
|
|
|
# load planet images |
112
|
|
|
global planetImgs |
113
|
|
|
global planetImgCnt |
114
|
|
|
planetImgs = {} |
115
|
|
|
planetImgCnt = {} |
116
|
|
|
for filename in glob.glob(resources.get('system/planet_*.png')): |
117
|
|
|
matchobj = re.search("planet_((.)[^.]+).png", filename) |
118
|
|
|
name = matchobj.group(1) |
119
|
|
|
pltype = matchobj.group(2) |
120
|
|
|
if pltype in planetImgCnt: |
121
|
|
|
planetImgCnt[pltype] += 1 |
122
|
|
|
else: |
123
|
|
|
planetImgCnt[pltype] = 1 |
124
|
|
|
planetImgs[name] = pygame.image.load(filename).convert_alpha() |
125
|
|
|
curr += 1 |
126
|
|
|
updateProgress(curr, progress_dlg) |
127
|
|
|
# load ship imgs |
128
|
|
|
global shipImgs |
129
|
|
|
shipImgs = {} |
130
|
|
|
for filename in glob.glob(resources.get('ships/??.png')): |
131
|
|
|
name = os.path.splitext(os.path.basename(filename))[0] |
132
|
|
|
shipImgs[int(name)] = pygame.image.load(filename).convert_alpha() |
133
|
|
|
curr += 1 |
134
|
|
|
updateProgress(curr, progress_dlg) |
135
|
|
|
# load star imgs |
136
|
|
|
global icons |
137
|
|
|
icons = {} |
138
|
|
|
for filename in glob.glob(resources.get('icons/[!ui_]*.png')): |
139
|
|
|
name = os.path.splitext(os.path.basename(filename))[0] |
140
|
|
|
icons[name] = pygame.image.load(filename).convert_alpha() |
141
|
|
|
curr += 1 |
142
|
|
|
updateProgress(curr, progress_dlg) |
143
|
|
|
# load UI icons |
144
|
|
|
global ui_icons |
145
|
|
|
ui_icons = {} |
146
|
|
|
for filename in glob.glob(resources.get('icons/ui_*.png')): |
147
|
|
|
name = os.path.splitext(os.path.basename(filename))[0] |
148
|
|
|
ui_icons[name] = pygame.image.load(filename).convert_alpha() |
149
|
|
|
curr += 1 |
150
|
|
|
updateProgress(curr, progress_dlg) |
151
|
|
|
# load buttons |
152
|
|
|
global buttonImgs |
153
|
|
|
buttonImgs = {} |
154
|
|
|
for filename in glob.glob(resources.get('buttons/*.png')): |
155
|
|
|
name = os.path.splitext(os.path.basename(filename))[0] |
156
|
|
|
buttonImgs[name] = pygame.image.load(filename).convert_alpha() |
157
|
|
|
curr += 1 |
158
|
|
|
updateProgress(curr, progress_dlg) |
159
|
|
|
# other icons |
160
|
|
|
global cmdInProgressImg |
161
|
|
|
cmdInProgressImg = pygame.image.load(resources.get('cmdInProgress.png')).convert_alpha() |
162
|
|
|
global structProblemImg |
163
|
|
|
structProblemImg = pygame.image.load(resources.get('struct_problem.png')).convert_alpha() |
164
|
|
|
global structOffImg |
165
|
|
|
structOffImg = pygame.image.load(resources.get('struct_off.png')).convert_alpha() |
166
|
|
|
|
167
|
|
|
|
168
|
|
|
def prepareUIIcons(color): |
169
|
|
|
for image in ui_icons.values(): |
170
|
|
|
image.fill((0, 0, 0, 255), None, pygame.BLEND_RGBA_MULT) |
171
|
|
|
image.fill(color[0:3] + (0, ), None, pygame.BLEND_RGBA_ADD) |
172
|
|
|
|
173
|
|
|
|
174
|
|
|
def getUIIcon(icon_name): |
175
|
|
|
return ui_icons["ui_" + str(icon_name)] |
176
|
|
|
|
177
|
|
|
|
178
|
|
|
def getTechImg(techID): |
179
|
|
|
return techImgs.get(techID, techImgs[0]) |
180
|
|
|
|
181
|
|
|
|
182
|
|
|
def getShipImg(combatClass, isMilitary): |
183
|
|
|
return shipImgs.get(int(combatClass) * 10 + int(isMilitary), shipImgs[99]) |
|
|
|
|
184
|
|
|
|
185
|
|
|
|
186
|
|
|
def getSmallStarImg(name): |
187
|
|
|
return smallStarImgs[name] |
188
|
|
|
|
189
|
|
|
|
190
|
|
|
def getBigStarImg(name): |
191
|
|
|
return bigStarImgs[name] |
192
|
|
|
|
193
|
|
|
|
194
|
|
|
def getPlanetImg(pltype, plid): |
195
|
|
|
global planetImgCnt |
196
|
|
|
name = '%s%d' % (pltype, plid % planetImgCnt[pltype]) |
197
|
|
|
return planetImgs[name] |
198
|
|
|
|
199
|
|
|
|
200
|
|
|
def getButton(name, state): |
201
|
|
|
if state: |
202
|
|
|
name = "%s_%s" % (name, 'active') |
203
|
|
|
else: |
204
|
|
|
name = "%s_%s" % (name, 'inactive') |
205
|
|
|
return buttonImgs[name] |
206
|
|
|
|
207
|
|
|
|
208
|
|
|
def getUnknownName(): |
209
|
|
|
return _('[Unknown]') |
210
|
|
|
|
211
|
|
|
|
212
|
|
|
def getNA(): |
213
|
|
|
return _('N/A') |
214
|
|
|
|
215
|
|
|
|
216
|
|
|
def getSystemOverviewProblemColor(owner, problem): |
217
|
|
|
if problem: |
218
|
|
|
return gdata.sevColors[gdata.CRI] |
219
|
|
|
else: |
220
|
|
|
return getPlayerColor(owner) |
221
|
|
|
|
222
|
|
|
|
223
|
|
|
def getFFColorCode(relationship): |
224
|
|
|
if relationship == Const.REL_UNDEF: |
225
|
|
|
return (0xc0, 0xc0, 0xc0) |
226
|
|
|
if relationship == Const.REL_UNITY: |
227
|
|
|
return (0x00, 0xff, 0x00) |
228
|
|
|
relColors = [(0xff, 0x80, 0x80), |
229
|
|
|
(0xff, 0x90, 0x01), |
230
|
|
|
(0xff, 0xff, 0x00), |
231
|
|
|
(0xb0, 0xb0, 0xff), |
232
|
|
|
(0x80, 0xff, 0xff)] |
233
|
|
|
return relColors[bisect.bisect(Const.REL_BOUNDARIES, relationship)] |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
def getHabitabilityColorCode(value): |
237
|
|
|
if value < 0: |
238
|
|
|
return (0xff, 0x00, 0xff) |
239
|
|
|
colorCodes = [(255, 5 * value, 0), # end 255, 125, 0 |
240
|
|
|
(255 - 2 * (value - 25), 125 + 2 * (value - 25), 0), # end 155, 225, 0 |
241
|
|
|
(155 - 3 * (value - 75), 225, 0), # end 5, 225, 0 |
242
|
|
|
(0, 255, 2 * (value - 125)), # end 0, 225, 250 |
243
|
|
|
(0, 255, 255)] |
244
|
|
|
boundaries = [25, 75, 125, 200] |
245
|
|
|
return colorCodes[bisect.bisect(boundaries, value)] |
246
|
|
|
|
247
|
|
|
|
248
|
|
|
def getPirateColonyColorCode(pirate): |
249
|
|
|
if pirate is False: |
250
|
|
|
return (0xc0, 0xc0, 0xc0) |
251
|
|
|
elif pirate == 0: |
252
|
|
|
return (0xff, 0x00, 0xff) |
253
|
|
|
else: |
254
|
|
|
# rest is the same as Habitability |
255
|
|
|
return getHabitabilityColorCode(pirate) |
256
|
|
|
|
257
|
|
|
def getFameColorCode(fame): |
258
|
|
|
if fame > 0 and fame < 200: |
259
|
|
|
# log.debug(fame, (0xff, 255 - int(255*(fame/200)), 0x00)) |
260
|
|
|
return (0xff, 255 - int(255 * (fame / 200.0)), 0x00) |
261
|
|
|
if fame == 200: |
262
|
|
|
return (0xff, 0x00, 0xff) # pirate colony |
263
|
|
|
return (0xc0, 0xc0, 0xc0) |
264
|
|
|
|
265
|
|
|
|
266
|
|
|
def getMineralColorCode(minerals): |
267
|
|
|
if minerals >= 0: |
268
|
|
|
# use min, since it we occasionally get 201+ mineral levels, |
269
|
|
|
# but it is so rare that we can ignore it for colors. |
270
|
|
|
return (0xff, max(0, min(255, 255 - int(255 * (minerals / 200.0)))), 0x0) |
271
|
|
|
return (0xc0, 0xc0, 0xc0) |
272
|
|
|
|
273
|
|
|
|
274
|
|
|
def getSlotColorCode(slots): |
275
|
|
|
if slots > 20: |
276
|
|
|
# in case sometime in the future we have larger worlds available |
277
|
|
|
return (0x0, 0xFF, min(255, (slots - 20) * 10)) |
278
|
|
|
if slots > 0: |
279
|
|
|
return (int(255 * (slots / 20.0)), 255 - int(255 * (slots / 20.0)), 0x0) |
280
|
|
|
return (0xc0, 0xc0, 0xc0) |
281
|
|
|
|
282
|
|
|
|
283
|
|
|
def getSlotSystemColorCode(slots, planets): |
284
|
|
|
if planets > 0: |
285
|
|
|
slots = int(float(slots) / planets) |
286
|
|
|
if slots > 20: |
287
|
|
|
# in case sometime in the future we have larger worlds available |
288
|
|
|
return (0x0, 0xFF, min(255, (slots - 20) * 10)) |
289
|
|
|
if slots > 0: |
290
|
|
|
return (int(255 * (slots / 20.0)), 255 - int(255 * (slots / 20.0)), 0x0) |
291
|
|
|
return (0xc0, 0xc0, 0xc0) |
292
|
|
|
else: |
293
|
|
|
return (0xc0, 0xc0, 0xc0) |
294
|
|
|
|
295
|
|
|
|
296
|
|
|
def getStargateColorCode(accel): |
297
|
|
|
accel = accel * 100 - 100 |
298
|
|
|
colorCodes = [(0xc0, 0xc0, 0xc0), |
299
|
|
|
(0xff, 0xff, 0x00), |
300
|
|
|
(0xff, 0xc0, 0x00), |
301
|
|
|
(0xff, 0x60, 0x00), |
302
|
|
|
(0xff, 0x00, 0x00), |
303
|
|
|
(0xff, 0x00, 0x80), |
304
|
|
|
(0xff, 0x00, 0xff)] |
305
|
|
|
boundaries = [1, 50, 150, 250, 350, 450] |
306
|
|
|
return colorCodes[bisect.bisect(boundaries, accel)] |
307
|
|
|
|
308
|
|
|
|
309
|
|
|
def getDockColorCode(refuel, upgrades): |
310
|
|
|
# refuel is based on best dock; upgrades are based on sum of all docks |
311
|
|
|
# refuel range: 0...6 |
312
|
|
|
# upgrade range: 0...100 (cap 100) |
313
|
|
|
if (refuel > 1 and upgrades > 0) or (refuel > 2): |
314
|
|
|
# refuel > 2 for other player docks since they always read upgrades of 0 |
315
|
|
|
# this probably should be corrected for allies |
316
|
|
|
refuelScale = max(0, min(1, (refuel - 1) / 5.0)) |
317
|
|
|
upgradeScale = max(0, min(1, upgrades / 50)) |
318
|
|
|
return (0xFF, int(refuelScale * (1 - upgradeScale) * 255), int(refuelScale * (upgradeScale) * 255)) |
319
|
|
|
if refuel > 0: |
320
|
|
|
refuelScale = max(0, min(1, refuel / 2.0)) |
321
|
|
|
return (0x00, 100 + 100 * int(refuelScale), 100 + 100 * int(refuelScale)) # cyan |
322
|
|
|
return (0xc0, 0xc0, 0xc0) |
323
|
|
|
|
324
|
|
|
|
325
|
|
|
def getMoraleColors(morale): |
326
|
|
|
if morale >= 0: |
327
|
|
|
return (255 - int(255 * (morale / 100.0)), int(255 * (morale / 100.0)), 0x0) |
328
|
|
|
return (0xc0, 0xc0, 0xc0) |
329
|
|
|
|
330
|
|
|
|
331
|
|
|
def getPlayerColor(owner, onlyDiplo = False): |
332
|
|
|
if owner == Const.OID_NONE: |
333
|
|
|
return getFFColorCode(Const.REL_UNDEF) |
334
|
|
|
if not onlyDiplo: |
335
|
|
|
if gdata.config.defaults.highlights == 'yes': |
336
|
|
|
if gdata.playersHighlightColors.has_key(owner): |
337
|
|
|
return gdata.playersHighlightColors[owner] |
338
|
|
|
rel = min(Const.REL_UNDEF, client.getRelationTo(owner)) |
339
|
|
|
return getFFColorCode(rel) |
340
|
|
|
|
341
|
|
|
|
342
|
|
|
def getControlColor(owner, onlyDiplo = False): |
343
|
|
|
if owner == Const.OID_NONE: |
344
|
|
|
return False |
345
|
|
|
if not onlyDiplo: |
346
|
|
|
if gdata.config.defaults.highlights == 'yes': |
347
|
|
|
if gdata.playersHighlightColors.has_key(owner): |
348
|
|
|
return fadeDarkColor(gdata.playersHighlightColors[owner]) |
349
|
|
|
rel = min(Const.REL_UNDEF, client.getRelationTo(owner)) |
350
|
|
|
return fadeDarkColor(getFFColorCode(rel)) |
351
|
|
|
|
352
|
|
|
|
353
|
|
|
def getGateLineWidth(owner): |
354
|
|
|
if owner == Const.OID_NONE: |
355
|
|
|
return 1 |
356
|
|
|
rel = min(Const.REL_UNDEF, client.getRelationTo(owner)) |
357
|
|
|
if rel == 1250: |
358
|
|
|
return 2 |
359
|
|
|
return 1 |
360
|
|
|
|
361
|
|
|
|
362
|
|
|
def getStarmapWidgetPlanetColor(ownerid, bio, mineral, slot, |
363
|
|
|
stargate, dockfuel, dockupgrade, fame, |
364
|
|
|
stratres, morale, pirate=False): |
365
|
|
|
colors = {} |
366
|
|
|
for datatype in gdata.OVERLAY_TYPES: |
367
|
|
|
colors[datatype] = getStarmapWidgetPlanetColorPerDatatype(datatype, ownerid, bio, mineral, slot, |
368
|
|
|
stargate, dockfuel, dockupgrade, fame, |
369
|
|
|
stratres, morale, pirate) |
370
|
|
|
return colors |
371
|
|
|
|
372
|
|
|
|
373
|
|
|
def getStarmapWidgetSystemColor(ownerid, bio, mineral, slot, |
374
|
|
|
num_planets, stargate, dockfuel, dockupgrade, fame, |
375
|
|
|
stratres, morale, pirate=False): |
376
|
|
|
# systems follow the same logic of the planets |
377
|
|
|
colors = getStarmapWidgetPlanetColor(ownerid, bio, mineral, slot, |
378
|
|
|
stargate, dockfuel, dockupgrade, fame, |
379
|
|
|
stratres, morale, pirate) |
380
|
|
|
# system has only one difference, which we have to override |
381
|
|
|
colors[gdata.OVERLAY_SLOT] = getSlotSystemColorCode(slot, num_planets) |
382
|
|
|
return colors |
383
|
|
|
|
384
|
|
|
|
385
|
|
|
def getStarmapWidgetPlanetColorPerDatatype(datatype, ownerid, bio, mineral, slot, |
386
|
|
|
stargate, dockfuel, dockupgrade, fame, |
387
|
|
|
stratres, morale, pirate): |
388
|
|
|
if (datatype == gdata.OVERLAY_OWNER): |
389
|
|
|
return getPlayerColor(ownerid) |
390
|
|
|
if (datatype == gdata.OVERLAY_DIPLO): |
391
|
|
|
return getPlayerColor(ownerid, True) |
392
|
|
|
if (datatype == gdata.OVERLAY_BIO): |
393
|
|
|
return getHabitabilityColorCode(bio) |
394
|
|
|
if (datatype == gdata.OVERLAY_MIN): |
395
|
|
|
return getMineralColorCode(mineral) |
396
|
|
|
if (datatype == gdata.OVERLAY_SLOT): |
397
|
|
|
return getSlotColorCode(slot) |
398
|
|
|
if (datatype == gdata.OVERLAY_STARGATE): |
399
|
|
|
return getStargateColorCode(stargate) |
400
|
|
|
if (datatype == gdata.OVERLAY_DOCK): |
401
|
|
|
return getDockColorCode(dockfuel, dockupgrade) |
402
|
|
|
if (datatype == gdata.OVERLAY_FAME): |
403
|
|
|
return getFameColorCode(fame) |
404
|
|
|
if (datatype == gdata.OVERLAY_PIRATECOLONYCOST): |
405
|
|
|
return getPirateColonyColorCode(pirate) |
406
|
|
|
if (datatype == gdata.OVERLAY_MORALE): |
407
|
|
|
return getMoraleColors(morale) |
408
|
|
|
return getPlayerColor(ownerid) # default |
409
|
|
|
|
410
|
|
|
|
411
|
|
|
def fadeColor(triplet): |
412
|
|
|
return ((triplet[0] + 0xc0) / 2, (triplet[1] + 0xc0) / 2, (triplet[2] + 0xc0) / 2) |
413
|
|
|
|
414
|
|
|
|
415
|
|
|
def fadeDarkColor(triplet): |
416
|
|
|
return ((triplet[0] + 0x00 * 2) / 3, (triplet[1] + 0x00 * 2) / 3, (triplet[2] + 0x00 * 2) / 3) |
417
|
|
|
|
418
|
|
|
|
419
|
|
|
def formatTime(time, separator=':'): |
420
|
|
|
time = int(math.ceil(time)) |
421
|
|
|
sign = '' |
422
|
|
|
if time < 0: |
423
|
|
|
time = - time |
424
|
|
|
sign = '-' |
425
|
|
|
days = time / Rules.turnsPerDay |
426
|
|
|
hours = time % Rules.turnsPerDay |
427
|
|
|
return '%s%d%s%02d' % (sign, days, separator, hours) |
428
|
|
|
|
429
|
|
|
|
430
|
|
|
def formatBE(b, e): |
431
|
|
|
return '%d / %d' % (b, e) |
432
|
|
|
|
433
|
|
|
|
434
|
|
|
def globalQueueName(index): |
435
|
|
|
return ['Default', 'Red', 'Blue', 'Yellow', 'Violet'][index] |
436
|
|
|
|