Suppress useless pylint warnings.
[super-star-trek.git] / sst.py
1 #!/usr/bin/env python
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 import os, sys, math, curses, time, pickle, random, copy, gettext, getpass
15 import getopt, socket, locale
16
17 # This import only works on Unixes.  The intention is to enable
18 # Ctrl-P, Ctrl-N, and friends in Cmd.
19 try:
20     import readline
21 except ImportError:
22     pass
23
24 version = "2.1"
25
26 docpath         = (".", "../doc", "/usr/share/doc/sst")
27
28 def _(st):
29     return gettext.gettext(st)
30
31 GALSIZE         = 8             # Galaxy size in quadrants
32 NINHAB          = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
33 MAXUNINHAB      = 10            # Maximum uninhabited worlds
34 QUADSIZE        = 10            # Quadrant size in sectors
35 BASEMIN         = 2                             # Minimum starbases
36 BASEMAX         = (GALSIZE * GALSIZE // 12)     # Maximum starbases
37 MAXKLGAME       = 127           # Maximum Klingons per game
38 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
39 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong
40 FOREVER         = 1e30          # Time for the indefinite future
41 MAXBURST        = 3             # Max # of torps you can launch in one turn
42 MINCMDR         = 10            # Minimum number of Klingon commanders
43 DOCKFAC         = 0.25          # Repair faster when docked
44 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
45
46 DEFAULT      = -1
47 BLACK        = 0
48 BLUE         = 1
49 GREEN        = 2
50 CYAN         = 3
51 RED          = 4
52 MAGENTA      = 5
53 BROWN        = 6
54 LIGHTGRAY    = 7
55 DARKGRAY     = 8
56 LIGHTBLUE    = 9
57 LIGHTGREEN   = 10
58 LIGHTCYAN    = 11
59 LIGHTRED     = 12
60 LIGHTMAGENTA = 13
61 YELLOW       = 14
62 WHITE        = 15
63
64 class TrekError(Exception):
65     pass
66
67 class JumpOut(Exception):
68     pass
69
70 class Coord:
71     def __init__(self, x=None, y=None):
72         self.i = x
73         self.j = y
74     def valid_quadrant(self):
75         return self.i >= 0 and self.i < GALSIZE and self.j >= 0 and self.j < GALSIZE
76     def valid_sector(self):
77         return self.i >= 0 and self.i < QUADSIZE and self.j >= 0 and self.j < QUADSIZE
78     def invalidate(self):
79         self.i = self.j = None
80     def is_valid(self):
81         return self.i != None and self.j != None
82     def __eq__(self, other):
83         return other != None and self.i == other.i and self.j == other.j
84     def __ne__(self, other):
85         return other is None or self.i != other.i or self.j != other.j
86     def __add__(self, other):
87         return Coord(self.i+other.i, self.j+other.j)
88     def __sub__(self, other):
89         return Coord(self.i-other.i, self.j-other.j)
90     def __mul__(self, other):
91         return Coord(self.i*other, self.j*other)
92     def __rmul__(self, other):
93         return Coord(self.i*other, self.j*other)
94     def __div__(self, other):
95         return Coord(self.i/other, self.j/other)
96     def __mod__(self, other):
97         return Coord(self.i % other, self.j % other)
98     def __rdiv__(self, other):
99         return Coord(self.i/other, self.j/other)
100     def roundtogrid(self):
101         return Coord(int(round(self.i)), int(round(self.j)))
102     def distance(self, other=None):
103         if not other:
104             other = Coord(0, 0)
105         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
106     def bearing(self):
107         return 1.90985*math.atan2(self.j, self.i)
108     def sgn(self):
109         s = Coord()
110         if self.i == 0:
111             s.i = 0
112         else:
113             s.i = self.i / abs(self.i)
114         if self.j == 0:
115             s.j = 0
116         else:
117             s.j = self.j / abs(self.j)
118         return s
119     def quadrant(self):
120         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
121         return self.roundtogrid() / QUADSIZE
122     def sector(self):
123         return self.roundtogrid() % QUADSIZE
124     def scatter(self):
125         s = Coord()
126         s.i = self.i + randrange(-1, 2)
127         s.j = self.j + randrange(-1, 2)
128         return s
129     def __str__(self):
130         if self.i is None or self.j is None:
131             return "Nowhere"
132         return "%s - %s" % (self.i+1, self.j+1)
133     __repr__ = __str__
134
135 class Thingy(Coord):
136     "Do not anger the Space Thingy!"
137     def __init__(self):
138         Coord.__init__(self)
139         self.angered = False
140     def angry(self):
141         self.angered = True
142     def at(self, q):
143         return (q.i, q.j) == (self.i, self.j)
144
145 class Planet:
146     def __init__(self):
147         self.name = None        # string-valued if inhabited
148         self.quadrant = Coord()        # quadrant located
149         self.pclass = None        # could be ""M", "N", "O", or "destroyed"
150         self.crystals = "absent"# could be "mined", "present", "absent"
151         self.known = "unknown"        # could be "unknown", "known", "shuttle_down"
152         self.inhabited = False        # is it inhabited?
153     def __str__(self):
154         return self.name
155
156 class Quadrant:
157     def __init__(self):
158         self.stars = 0
159         self.planet = None
160         self.starbase = False
161         self.klingons = 0
162         self.romulans = 0
163         self.supernova = False
164         self.charted = False
165         self.status = "secure"        # Could be "secure", "distressed", "enslaved"
166
167 class Page:
168     def __init__(self):
169         self.stars = None
170         self.starbase = False
171         self.klingons = None
172     def __repr__(self):
173         return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars)
174
175 def fill2d(size, fillfun):
176     "Fill an empty list in 2D."
177     lst = []
178     for i in range(size):
179         lst.append([])
180         for j in range(size):
181             lst[i].append(fillfun(i, j))
182     return lst
183
184 class Snapshot:
185     def __init__(self):
186         self.snap = False        # snapshot taken
187         self.crew = 0           # crew complement
188         self.remkl = 0          # remaining klingons
189         self.nscrem = 0                # remaining super commanders
190         self.starkl = 0         # destroyed stars
191         self.basekl = 0         # destroyed bases
192         self.nromrem = 0        # Romulans remaining
193         self.nplankl = 0        # destroyed uninhabited planets
194         self.nworldkl = 0        # destroyed inhabited planets
195         self.planets = []        # Planet information
196         self.date = 0.0           # stardate
197         self.remres = 0         # remaining resources
198         self.remtime = 0        # remaining time
199         self.baseq = []         # Base quadrant coordinates
200         self.kcmdr = []         # Commander quadrant coordinates
201         self.kscmdr = Coord()        # Supercommander quadrant coordinates
202         # the galaxy
203         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
204         # the starchart
205         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
206
207 class Event:
208     def __init__(self):
209         self.date = None        # A real number
210         self.quadrant = None        # A coord structure
211
212 # game options
213 OPTION_ALL        = 0xffffffff
214 OPTION_TTY        = 0x00000001        # old interface
215 OPTION_CURSES        = 0x00000002        # new interface
216 OPTION_IOMODES        = 0x00000003        # cover both interfaces
217 OPTION_PLANETS        = 0x00000004        # planets and mining
218 OPTION_THOLIAN        = 0x00000008        # Tholians and their webs (UT 1979 version)
219 OPTION_THINGY        = 0x00000010        # Space Thingy can shoot back (Stas, 2005)
220 OPTION_PROBE        = 0x00000020        # deep-space probes (DECUS version, 1980)
221 OPTION_SHOWME        = 0x00000040        # bracket Enterprise in chart
222 OPTION_RAMMING        = 0x00000080        # enemies may ram Enterprise (Almy)
223 OPTION_MVBADDY        = 0x00000100        # more enemies can move (Almy)
224 OPTION_BLKHOLE        = 0x00000200        # black hole may timewarp you (Stas, 2005)
225 OPTION_BASE        = 0x00000400        # bases have good shields (Stas, 2005)
226 OPTION_WORLDS        = 0x00000800        # logic for inhabited worlds (ESR, 2006)
227 OPTION_AUTOSCAN        = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
228 OPTION_PLAIN        = 0x01000000        # user chose plain game
229 OPTION_ALMY        = 0x02000000        # user chose Almy variant
230 OPTION_COLOR    = 0x04000000        # enable color display (experimental, ESR, 2010)
231
232 # Define devices
233 DSRSENS         = 0
234 DLRSENS         = 1
235 DPHASER         = 2
236 DPHOTON         = 3
237 DLIFSUP         = 4
238 DWARPEN         = 5
239 DIMPULS         = 6
240 DSHIELD         = 7
241 DRADIO         = 0
242 DSHUTTL  = 9
243 DCOMPTR  = 10
244 DNAVSYS         = 11
245 DTRANSP  = 12
246 DSHCTRL         = 13
247 DDRAY         = 14
248 DDSP         = 15
249 NDEVICES = 16        # Number of devices
250
251 SKILL_NONE        = 0
252 SKILL_NOVICE        = 1
253 SKILL_FAIR        = 2
254 SKILL_GOOD        = 3
255 SKILL_EXPERT        = 4
256 SKILL_EMERITUS        = 5
257
258 def damaged(dev):
259     return (game.damage[dev] != 0.0)
260 def communicating():
261     return not damaged(DRADIO) or game.condition=="docked"
262
263 # Define future events
264 FSPY        = 0        # Spy event happens always (no future[] entry)
265                 # can cause SC to tractor beam Enterprise
266 FSNOVA  = 1        # Supernova
267 FTBEAM  = 2        # Commander tractor beams Enterprise
268 FSNAP   = 3        # Snapshot for time warp
269 FBATTAK = 4        # Commander attacks base
270 FCDBAS  = 5        # Commander destroys base
271 FSCMOVE = 6        # Supercommander moves (might attack base)
272 FSCDBAS = 7        # Supercommander destroys base
273 FDSPROB = 8        # Move deep space probe
274 FDISTR        = 9        # Emit distress call from an inhabited world
275 FENSLV        = 10        # Inhabited word is enslaved */
276 FREPRO        = 11        # Klingons build a ship in an enslaved system
277 NEVENTS        = 12
278
279 # Abstract out the event handling -- underlying data structures will change
280 # when we implement stateful events
281 def findevent(evtype):
282     return game.future[evtype]
283
284 class Enemy:
285     def __init__(self, etype=None, loc=None, power=None):
286         self.type = etype
287         self.location = Coord()
288         self.kdist = None
289         self.kavgd = None
290         if loc:
291             self.move(loc)
292         self.power = power        # enemy energy level
293         game.enemies.append(self)
294     def move(self, loc):
295         motion = (loc != self.location)
296         if self.location.i is not None and self.location.j is not None:
297             if motion:
298                 if self.type == 'T':
299                     game.quad[self.location.i][self.location.j] = '#'
300                 else:
301                     game.quad[self.location.i][self.location.j] = '.'
302         if loc:
303             self.location = copy.copy(loc)
304             game.quad[self.location.i][self.location.j] = self.type
305             self.kdist = self.kavgd = (game.sector - loc).distance()
306         else:
307             self.location = Coord()
308             self.kdist = self.kavgd = None
309             game.enemies.remove(self)
310         return motion
311     def __repr__(self):
312         return "<%s,%s.%f>" % (self.type, self.location, self.power)        # For debugging
313
314 class Gamestate:
315     def __init__(self):
316         self.options = None        # Game options
317         self.state = Snapshot()        # A snapshot structure
318         self.snapsht = Snapshot()        # Last snapshot taken for time-travel purposes
319         self.quad = None        # contents of our quadrant
320         self.damage = [0.0] * NDEVICES        # damage encountered
321         self.future = []        # future events
322         i = NEVENTS
323         while i > 0:
324             i -= 1
325             self.future.append(Event())
326         self.passwd  = None        # Self Destruct password
327         self.enemies = []
328         self.quadrant = None        # where we are in the large
329         self.sector = None        # where we are in the small
330         self.tholian = None        # Tholian enemy object
331         self.base = None        # position of base in current quadrant
332         self.battle = None        # base coordinates being attacked
333         self.plnet = None        # location of planet in quadrant
334         self.gamewon = False        # Finished!
335         self.ididit = False        # action taken -- allows enemy to attack
336         self.alive = False        # we are alive (not killed)
337         self.justin = False        # just entered quadrant
338         self.shldup = False        # shields are up
339         self.shldchg = False        # shield is changing (affects efficiency)
340         self.iscate = False        # super commander is here
341         self.ientesc = False        # attempted escape from supercommander
342         self.resting = False        # rest time
343         self.icraft = False        # Kirk in Galileo
344         self.landed = False        # party on planet (true), on ship (false)
345         self.alldone = False        # game is now finished
346         self.neutz = False        # Romulan Neutral Zone
347         self.isarmed = False        # probe is armed
348         self.inorbit = False        # orbiting a planet
349         self.imine = False        # mining
350         self.icrystl = False        # dilithium crystals aboard
351         self.iseenit = False        # seen base attack report
352         self.thawed = False        # thawed game
353         self.condition = None        # "green", "yellow", "red", "docked", "dead"
354         self.iscraft = None        # "onship", "offship", "removed"
355         self.skill = None        # Player skill level
356         self.inkling = 0        # initial number of klingons
357         self.inbase = 0                # initial number of bases
358         self.incom = 0                # initial number of commanders
359         self.inscom = 0                # initial number of commanders
360         self.inrom = 0                # initial number of commanders
361         self.instar = 0                # initial stars
362         self.intorps = 0        # initial/max torpedoes
363         self.torps = 0                # number of torpedoes
364         self.ship = 0                # ship type -- 'E' is Enterprise
365         self.abandoned = 0        # count of crew abandoned in space
366         self.length = 0                # length of game
367         self.klhere = 0                # klingons here
368         self.casual = 0                # causalties
369         self.nhelp = 0                # calls for help
370         self.nkinks = 0                # count of energy-barrier crossings
371         self.iplnet = None        # planet # in quadrant
372         self.inplan = 0                # initial planets
373         self.irhere = 0                # Romulans in quadrant
374         self.isatb = 0                # =2 if super commander is attacking base
375         self.tourn = None        # tournament number
376         self.nprobes = 0        # number of probes available
377         self.inresor = 0.0        # initial resources
378         self.intime = 0.0        # initial time
379         self.inenrg = 0.0        # initial/max energy
380         self.inshld = 0.0        # initial/max shield
381         self.inlsr = 0.0        # initial life support resources
382         self.indate = 0.0        # initial date
383         self.energy = 0.0        # energy level
384         self.shield = 0.0        # shield level
385         self.warpfac = 0.0        # warp speed
386         self.lsupres = 0.0        # life support reserves
387         self.optime = 0.0        # time taken by current operation
388         self.damfac = 0.0        # damage factor
389         self.lastchart = 0.0        # time star chart was last updated
390         self.cryprob = 0.0        # probability that crystal will work
391         self.probe = None        # object holding probe course info
392         self.height = 0.0        # height of orbit around planet
393         self.score = 0.0        # overall score
394         self.perdate = 0.0        # rate of kills
395         self.idebug = False        # Debugging instrumentation enabled?
396         self.statekscmdr = None # No SuperCommander coordinates yet.
397     def recompute(self):
398         # Stas thinks this should be (C expression):
399         # game.state.remkl + len(game.state.kcmdr) > 0 ?
400         #        game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
401         # He says the existing expression is prone to divide-by-zero errors
402         # after killing the last klingon when score is shown -- perhaps also
403         # if the only remaining klingon is SCOM.
404         self.state.remtime = self.state.remres/(self.state.remkl + 4*len(self.state.kcmdr))
405
406 FWON = 0
407 FDEPLETE = 1
408 FLIFESUP = 2
409 FNRG = 3
410 FBATTLE = 4
411 FNEG3 = 5
412 FNOVA = 6
413 FSNOVAED = 7
414 FABANDN = 8
415 FDILITHIUM = 9
416 FMATERIALIZE = 10
417 FPHASER = 11
418 FLOST = 12
419 FMINING = 13
420 FDPLANET = 14
421 FPNOVA = 15
422 FSSC = 16
423 FSTRACTOR = 17
424 FDRAY = 18
425 FTRIBBLE = 19
426 FHOLE = 20
427 FCREW = 21
428
429 def withprob(p):
430     return random.random() < p
431
432 def randrange(*args):
433     return random.randrange(*args)
434
435 def randreal(*args):
436     v = random.random()
437     if len(args) == 1:
438         v *= args[0]                 # from [0, args[0])
439     elif len(args) == 2:
440         v = args[0] + v*(args[1]-args[0])        # from [args[0], args[1])
441     return v
442
443 # Code from ai.c begins here
444
445 def welcoming(iq):
446     "Would this quadrant welcome another Klingon?"
447     return iq.valid_quadrant() and \
448         not game.state.galaxy[iq.i][iq.j].supernova and \
449         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
450
451 def tryexit(enemy, look, irun):
452     "A bad guy attempts to bug out."
453     iq = Coord()
454     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
455     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
456     if not welcoming(iq):
457         return False
458     if enemy.type == 'R':
459         return False # Romulans cannot escape!
460     if not irun:
461         # avoid intruding on another commander's territory
462         if enemy.type == 'C':
463             if iq in game.state.kcmdr:
464                 return []
465             # refuse to leave if currently attacking starbase
466             if game.battle == game.quadrant:
467                 return []
468         # don't leave if over 1000 units of energy
469         if enemy.power > 1000.0:
470             return []
471     oldloc = copy.copy(enemy.location)
472     # handle local matters related to escape
473     enemy.move(None)
474     game.klhere -= 1
475     if game.condition != "docked":
476         newcnd()
477     # Handle global matters related to escape
478     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
479     game.state.galaxy[iq.i][iq.j].klingons += 1
480     if enemy.type == 'S':
481         game.iscate = False
482         game.ientesc = False
483         game.isatb = 0
484         schedule(FSCMOVE, 0.2777)
485         unschedule(FSCDBAS)
486         game.state.kscmdr = iq
487     else:
488         for cmdr in game.state.kcmdr:
489             if cmdr == game.quadrant:
490                 game.state.kcmdr.append(iq)
491                 break
492     # report move out of quadrant.
493     return [(True, enemy, oldloc, iq)]
494
495 # The bad-guy movement algorithm:
496 #
497 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
498 # If both are operating full strength, force is 1000. If both are damaged,
499 # force is -1000. Having shields down subtracts an additional 1000.
500 #
501 # 2. Enemy has forces equal to the energy of the attacker plus
502 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
503 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
504 #
505 # Attacker Initial energy levels (nominal):
506 # Klingon   Romulan   Commander   Super-Commander
507 # Novice    400        700        1200
508 # Fair      425        750        1250
509 # Good      450        800        1300        1750
510 # Expert    475        850        1350        1875
511 # Emeritus  500        900        1400        2000
512 # VARIANCE   75        200         200         200
513 #
514 # Enemy vessels only move prior to their attack. In Novice - Good games
515 # only commanders move. In Expert games, all enemy vessels move if there
516 # is a commander present. In Emeritus games all enemy vessels move.
517 #
518 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
519 # forces are 1000 greater than Enterprise.
520 #
521 # Agressive action on average cuts the distance between the ship and
522 # the enemy to 1/4 the original.
523 #
524 # 4.  At lower energy advantage, movement units are proportional to the
525 # advantage with a 650 advantage being to hold ground, 800 to move forward
526 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
527 #
528 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
529 # retreat, especially at high skill levels.
530 #
531 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
532
533 def movebaddy(enemy):
534     "Tactical movement for the bad guys."
535     goto = Coord()
536     look = Coord()
537     irun = False
538     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
539     if game.skill >= SKILL_EXPERT:
540         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
541     else:
542         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
543     old_dist = enemy.kdist
544     mdist = int(old_dist + 0.5) # Nearest integer distance
545     # If SC, check with spy to see if should hi-tail it
546     if enemy.type == 'S' and \
547         (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
548         irun = True
549         motion = -QUADSIZE
550     else:
551         # decide whether to advance, retreat, or hold position
552         forces = enemy.power+100.0*len(game.enemies)+400*(nbaddys-1)
553         if not game.shldup:
554             forces += 1000 # Good for enemy if shield is down!
555         if not damaged(DPHASER) or not damaged(DPHOTON):
556             if damaged(DPHASER): # phasers damaged
557                 forces += 300.0
558             else:
559                 forces -= 0.2*(game.energy - 2500.0)
560             if damaged(DPHOTON): # photon torpedoes damaged
561                 forces += 300.0
562             else:
563                 forces -= 50.0*game.torps
564         else:
565             # phasers and photon tubes both out!
566             forces += 1000.0
567         motion = 0
568         if forces <= 1000.0 and game.condition != "docked": # Typical situation
569             motion = ((forces + randreal(200))/150.0) - 5.0
570         else:
571             if forces > 1000.0: # Very strong -- move in for kill
572                 motion = (1.0 - randreal())**2 * old_dist + 1.0
573             if game.condition == "docked" and (game.options & OPTION_BASE): # protected by base -- back off !
574                 motion -= game.skill*(2.0-randreal()**2)
575         if game.idebug:
576             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
577         # don't move if no motion
578         if motion == 0:
579             return []
580         # Limit motion according to skill
581         if abs(motion) > game.skill:
582             if motion < 0:
583                 motion = -game.skill
584             else:
585                 motion = game.skill
586     # calculate preferred number of steps
587     nsteps = abs(int(motion))
588     if motion > 0 and nsteps > mdist:
589         nsteps = mdist # don't overshoot
590     if nsteps > QUADSIZE:
591         nsteps = QUADSIZE # This shouldn't be necessary
592     if nsteps < 1:
593         nsteps = 1 # This shouldn't be necessary
594     if game.idebug:
595         proutn("NSTEPS = %d:" % nsteps)
596     # Compute preferred values of delta X and Y
597     m = game.sector - enemy.location
598     if 2.0 * abs(m.i) < abs(m.j):
599         m.i = 0
600     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
601         m.j = 0
602     m = (motion * m).sgn()
603     goto = enemy.location
604     # main move loop
605     for ll in range(nsteps):
606         if game.idebug:
607             proutn(" %d" % (ll+1))
608         # Check if preferred position available
609         look = goto + m
610         if m.i < 0:
611             krawli = 1
612         else:
613             krawli = -1
614         if m.j < 0:
615             krawlj = 1
616         else:
617             krawlj = -1
618         success = False
619         attempts = 0 # Settle mysterious hang problem
620         while attempts < 20 and not success:
621             attempts += 1
622             if look.i < 0 or look.i >= QUADSIZE:
623                 if motion < 0:
624                     return tryexit(enemy, look, irun)
625                 if krawli == m.i or m.j == 0:
626                     break
627                 look.i = goto.i + krawli
628                 krawli = -krawli
629             elif look.j < 0 or look.j >= QUADSIZE:
630                 if motion < 0:
631                     return tryexit(enemy, look, irun)
632                 if krawlj == m.j or m.i == 0:
633                     break
634                 look.j = goto.j + krawlj
635                 krawlj = -krawlj
636             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
637                 # See if enemy should ram ship
638                 if game.quad[look.i][look.j] == game.ship and \
639                     (enemy.type == 'C' or enemy.type == 'S'):
640                     collision(rammed=True, enemy=enemy)
641                     return []
642                 if krawli != m.i and m.j != 0:
643                     look.i = goto.i + krawli
644                     krawli = -krawli
645                 elif krawlj != m.j and m.i != 0:
646                     look.j = goto.j + krawlj
647                     krawlj = -krawlj
648                 else:
649                     break # we have failed
650             else:
651                 success = True
652         if success:
653             goto = look
654             if game.idebug:
655                 proutn(repr(goto))
656         else:
657             break # done early
658     if game.idebug:
659         skip(1)
660     # Enemy moved, but is still in sector
661     return [(False, enemy, old_dist, goto)]
662
663 def moveklings():
664     "Sequence Klingon tactical movement."
665     if game.idebug:
666         prout("== MOVCOM")
667     # Figure out which Klingon is the commander (or Supercommander)
668     # and do move
669     tacmoves = []
670     if game.quadrant in game.state.kcmdr:
671         for enemy in game.enemies:
672             if enemy.type == 'C':
673                 tacmoves += movebaddy(enemy)
674     if game.state.kscmdr == game.quadrant:
675         for enemy in game.enemies:
676             if enemy.type == 'S':
677                 tacmoves += movebaddy(enemy)
678                 break
679     # If skill level is high, move other Klingons and Romulans too!
680     # Move these last so they can base their actions on what the
681     # commander(s) do.
682     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
683         for enemy in game.enemies:
684             if enemy.type in ('K', 'R'):
685                 tacmoves += movebaddy(enemy)
686     return tacmoves
687
688 def movescom(iq, avoid):
689     "Commander movement helper."
690     # Avoid quadrants with bases if we want to avoid Enterprise
691     if not welcoming(iq) or (avoid and iq in game.state.baseq):
692         return False
693     if game.justin and not game.iscate:
694         return False
695     # do the move
696     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
697     game.state.kscmdr = iq
698     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
699     if game.state.kscmdr == game.quadrant:
700         # SC has scooted, remove him from current quadrant
701         game.iscate = False
702         game.isatb = 0
703         game.ientesc = False
704         unschedule(FSCDBAS)
705         for enemy in game.enemies:
706             if enemy.type == 'S':
707                 enemy.move(None)
708         game.klhere -= 1
709         if game.condition != "docked":
710             newcnd()
711         sortenemies()
712     # check for a helpful planet
713     for i in range(game.inplan):
714         if game.state.planets[i].quadrant == game.state.kscmdr and \
715             game.state.planets[i].crystals == "present":
716             # destroy the planet
717             game.state.planets[i].pclass = "destroyed"
718             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
719             if communicating():
720                 announce()
721                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
722                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
723                 prout(_("   by the Super-commander.\""))
724             break
725     return True # looks good!
726
727 def supercommander():
728     "Move the Super Commander."
729     iq = Coord()
730     sc = Coord()
731     ibq = Coord()
732     idelta = Coord()
733     basetbl = []
734     if game.idebug:
735         prout("== SUPERCOMMANDER")
736     # Decide on being active or passive
737     avoid = ((game.incom - len(game.state.kcmdr) + game.inkling - game.state.remkl)/(game.state.date+0.01-game.indate) < 0.1*game.skill*(game.skill+1.0) or \
738             (game.state.date-game.indate) < 3.0)
739     if not game.iscate and avoid:
740         # compute move away from Enterprise
741         idelta = game.state.kscmdr-game.quadrant
742         if idelta.distance() > 2.0:
743             # circulate in space
744             idelta.i = game.state.kscmdr.j-game.quadrant.j
745             idelta.j = game.quadrant.i-game.state.kscmdr.i
746     else:
747         # compute distances to starbases
748         if not game.state.baseq:
749             # nothing left to do
750             unschedule(FSCMOVE)
751             return
752         sc = game.state.kscmdr
753         for (i, base) in enumerate(game.state.baseq):
754             basetbl.append((i, (base - sc).distance()))
755         if game.state.baseq > 1:
756             basetbl.sort(key=lambda x: x[1])
757         # look for nearest base without a commander, no Enterprise, and
758         # without too many Klingons, and not already under attack.
759         ifindit = iwhichb = 0
760         for (i2, base) in enumerate(game.state.baseq):
761             i = basetbl[i2][0]        # bug in original had it not finding nearest
762             if base == game.quadrant or base == game.battle or not welcoming(base):
763                 continue
764             # if there is a commander, and no other base is appropriate,
765             # we will take the one with the commander
766             for cmdr in game.state.kcmdr:
767                 if base == cmdr and ifindit != 2:
768                     ifindit = 2
769                     iwhichb = i
770                     break
771             else:        # no commander -- use this one
772                 ifindit = 1
773                 iwhichb = i
774                 break
775         if ifindit == 0:
776             return # Nothing suitable -- wait until next time
777         ibq = game.state.baseq[iwhichb]
778         # decide how to move toward base
779         idelta = ibq - game.state.kscmdr
780     # Maximum movement is 1 quadrant in either or both axes
781     idelta = idelta.sgn()
782     # try moving in both x and y directions
783     # there was what looked like a bug in the Almy C code here,
784     # but it might be this translation is just wrong.
785     iq = game.state.kscmdr + idelta
786     if not movescom(iq, avoid):
787         # failed -- try some other maneuvers
788         if idelta.i == 0 or idelta.j == 0:
789             # attempt angle move
790             if idelta.i != 0:
791                 iq.j = game.state.kscmdr.j + 1
792                 if not movescom(iq, avoid):
793                     iq.j = game.state.kscmdr.j - 1
794                     movescom(iq, avoid)
795             elif idelta.j != 0:
796                 iq.i = game.state.kscmdr.i + 1
797                 if not movescom(iq, avoid):
798                     iq.i = game.state.kscmdr.i - 1
799                     movescom(iq, avoid)
800         else:
801             # try moving just in x or y
802             iq.j = game.state.kscmdr.j
803             if not movescom(iq, avoid):
804                 iq.j = game.state.kscmdr.j + idelta.j
805                 iq.i = game.state.kscmdr.i
806                 movescom(iq, avoid)
807     # check for a base
808     if len(game.state.baseq) == 0:
809         unschedule(FSCMOVE)
810     else:
811         for ibq in game.state.baseq:
812             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
813                 # attack the base
814                 if avoid:
815                     return # no, don't attack base!
816                 game.iseenit = False
817                 game.isatb = 1
818                 schedule(FSCDBAS, randreal(1.0, 3.0))
819                 if is_scheduled(FCDBAS):
820                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
821                 if not communicating():
822                     return # no warning
823                 game.iseenit = True
824                 announce()
825                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
826                       % game.state.kscmdr)
827                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
828                 proutn(_("   It can survive until stardate %d.\"") \
829                        % int(scheduled(FSCDBAS)))
830                 if not game.resting:
831                     return
832                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
833                 if not ja():
834                     return
835                 game.resting = False
836                 game.optime = 0.0 # actually finished
837                 return
838     # Check for intelligence report
839     if not game.idebug and \
840         (withprob(0.8) or \
841          (not communicating()) or \
842          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
843         return
844     announce()
845     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
846     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
847     return
848
849 def movetholian():
850     "Move the Tholian."
851     if not game.tholian or game.justin:
852         return
853     tid = Coord()
854     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
855         tid.i = 0
856         tid.j = QUADSIZE-1
857     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
858         tid.i = QUADSIZE-1
859         tid.j = QUADSIZE-1
860     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
861         tid.i = QUADSIZE-1
862         tid.j = 0
863     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
864         tid.i = 0
865         tid.j = 0
866     else:
867         # something is wrong!
868         game.tholian.move(None)
869         prout("***Internal error: Tholian in a bad spot.")
870         return
871     # do nothing if we are blocked
872     if game.quad[tid.i][tid.j] not in ('.', '#'):
873         return
874     here = copy.copy(game.tholian.location)
875     delta = (tid - game.tholian.location).sgn()
876     # move in x axis
877     while here.i != tid.i:
878         here.i += delta.i
879         if game.quad[here.i][here.j] == '.':
880             game.tholian.move(here)
881     # move in y axis
882     while here.j != tid.j:
883         here.j += delta.j
884         if game.quad[here.i][here.j] == '.':
885             game.tholian.move(here)
886     # check to see if all holes plugged
887     for i in range(QUADSIZE):
888         if game.quad[0][i] != '#' and game.quad[0][i] != 'T':
889             return
890         if game.quad[QUADSIZE-1][i] != '#' and game.quad[QUADSIZE-1][i] != 'T':
891             return
892         if game.quad[i][0] != '#' and game.quad[i][0] != 'T':
893             return
894         if game.quad[i][QUADSIZE-1] != '#' and game.quad[i][QUADSIZE-1] != 'T':
895             return
896     # All plugged up -- Tholian splits
897     game.quad[game.tholian.location.i][game.tholian.location.j] = '#'
898     dropin(' ')
899     prout(crmena(True, 'T', "sector", game.tholian) + _(" completes web."))
900     game.tholian.move(None)
901     return
902
903 # Code from battle.c begins here
904
905 def doshield(shraise):
906     "Change shield status."
907     action = "NONE"
908     game.ididit = False
909     if shraise:
910         action = "SHUP"
911     else:
912         key = scanner.nexttok()
913         if key == "IHALPHA":
914             if scanner.sees("transfer"):
915                 action = "NRG"
916             else:
917                 if damaged(DSHIELD):
918                     prout(_("Shields damaged and down."))
919                     return
920                 if scanner.sees("up"):
921                     action = "SHUP"
922                 elif scanner.sees("down"):
923                     action = "SHDN"
924         if action == "NONE":
925             proutn(_("Do you wish to change shield energy? "))
926             if ja():
927                 action = "NRG"
928             elif damaged(DSHIELD):
929                 prout(_("Shields damaged and down."))
930                 return
931             elif game.shldup:
932                 proutn(_("Shields are up. Do you want them down? "))
933                 if ja():
934                     action = "SHDN"
935                 else:
936                     scanner.chew()
937                     return
938             else:
939                 proutn(_("Shields are down. Do you want them up? "))
940                 if ja():
941                     action = "SHUP"
942                 else:
943                     scanner.chew()
944                     return
945     if action == "SHUP": # raise shields
946         if game.shldup:
947             prout(_("Shields already up."))
948             return
949         game.shldup = True
950         game.shldchg = True
951         if game.condition != "docked":
952             game.energy -= 50.0
953         prout(_("Shields raised."))
954         if game.energy <= 0:
955             skip(1)
956             prout(_("Shields raising uses up last of energy."))
957             finish(FNRG)
958             return
959         game.ididit = True
960         return
961     elif action == "SHDN":
962         if not game.shldup:
963             prout(_("Shields already down."))
964             return
965         game.shldup = False
966         game.shldchg = True
967         prout(_("Shields lowered."))
968         game.ididit = True
969         return
970     elif action == "NRG":
971         while scanner.nexttok() != "IHREAL":
972             scanner.chew()
973             proutn(_("Energy to transfer to shields- "))
974         nrg = scanner.real
975         scanner.chew()
976         if nrg == 0:
977             return
978         if nrg > game.energy:
979             prout(_("Insufficient ship energy."))
980             return
981         game.ididit = True
982         if game.shield+nrg >= game.inshld:
983             prout(_("Shield energy maximized."))
984             if game.shield+nrg > game.inshld:
985                 prout(_("Excess energy requested returned to ship energy"))
986             game.energy -= game.inshld-game.shield
987             game.shield = game.inshld
988             return
989         if nrg < 0.0 and game.energy-nrg > game.inenrg:
990             # Prevent shield drain loophole
991             skip(1)
992             prout(_("Engineering to bridge--"))
993             prout(_("  Scott here. Power circuit problem, Captain."))
994             prout(_("  I can't drain the shields."))
995             game.ididit = False
996             return
997         if game.shield+nrg < 0:
998             prout(_("All shield energy transferred to ship."))
999             game.energy += game.shield
1000             game.shield = 0.0
1001             return
1002         proutn(_("Scotty- \""))
1003         if nrg > 0:
1004             prout(_("Transferring energy to shields.\""))
1005         else:
1006             prout(_("Draining energy from shields.\""))
1007         game.shield += nrg
1008         game.energy -= nrg
1009         return
1010
1011 def randdevice():
1012     "Choose a device to damage, at random."
1013     weights = (
1014         105,        # DSRSENS: short range scanners        10.5%
1015         105,        # DLRSENS: long range scanners                10.5%
1016         120,        # DPHASER: phasers                        12.0%
1017         120,        # DPHOTON: photon torpedoes                12.0%
1018         25,        # DLIFSUP: life support                         2.5%
1019         65,        # DWARPEN: warp drive                         6.5%
1020         70,        # DIMPULS: impulse engines                 6.5%
1021         145,        # DSHIELD: deflector shields                14.5%
1022         30,        # DRADIO:  subspace radio                 3.0%
1023         45,        # DSHUTTL: shuttle                         4.5%
1024         15,        # DCOMPTR: computer                         1.5%
1025         20,        # NAVCOMP: navigation system                 2.0%
1026         75,        # DTRANSP: transporter                         7.5%
1027         20,        # DSHCTRL: high-speed shield controller  2.0%
1028         10,        # DDRAY: death ray                         1.0%
1029         30,        # DDSP: deep-space probes                 3.0%
1030     )
1031     assert(sum(weights) == 1000)
1032     idx = randrange(1000)
1033     wsum = 0
1034     for (i, w) in enumerate(weights):
1035         wsum += w
1036         if idx < wsum:
1037             return i
1038     return None        # we should never get here
1039
1040 def collision(rammed, enemy):
1041     "Collision handling for rammong events."
1042     prouts(_("***RED ALERT!  RED ALERT!"))
1043     skip(1)
1044     prout(_("***COLLISION IMMINENT."))
1045     skip(2)
1046     proutn("***")
1047     proutn(crmshp())
1048     hardness = {'R':1.5, 'C':2.0, 'S':2.5, 'T':0.5, '?':4.0}.get(enemy.type, 1.0)
1049     if rammed:
1050         proutn(_(" rammed by "))
1051     else:
1052         proutn(_(" rams "))
1053     proutn(crmena(False, enemy.type, "sector", enemy.location))
1054     if rammed:
1055         proutn(_(" (original position)"))
1056     skip(1)
1057     deadkl(enemy.location, enemy.type, game.sector)
1058     proutn("***" + crmshp() + " heavily damaged.")
1059     icas = randrange(10, 30)
1060     prout(_("***Sickbay reports %d casualties") % icas)
1061     game.casual += icas
1062     game.state.crew -= icas
1063     # In the pre-SST2K version, all devices got equiprobably damaged,
1064     # which was silly.  Instead, pick up to half the devices at
1065     # random according to our weighting table,
1066     ncrits = randrange(NDEVICES/2)
1067     while ncrits > 0:
1068         ncrits -= 1
1069         dev = randdevice()
1070         if game.damage[dev] < 0:
1071             continue
1072         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1073         # Damage for at least time of travel!
1074         game.damage[dev] += game.optime + extradm
1075     game.shldup = False
1076     prout(_("***Shields are down."))
1077     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1078         announce()
1079         damagereport()
1080     else:
1081         finish(FWON)
1082     return
1083
1084 def torpedo(origin, bearing, dispersion, number, nburst):
1085     "Let a photon torpedo fly"
1086     if not damaged(DSRSENS) or game.condition == "docked":
1087         setwnd(srscan_window)
1088     else:
1089         setwnd(message_window)
1090     ac = bearing + 0.25*dispersion        # dispersion is a random variable
1091     bullseye = (15.0 - bearing)*0.5235988
1092     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin))
1093     bumpto = Coord(0, 0)
1094     # Loop to move a single torpedo
1095     setwnd(message_window)
1096     for step in range(1, QUADSIZE*2):
1097         if not track.nexttok():
1098             break
1099         w = track.sector()
1100         if not w.valid_sector():
1101             break
1102         iquad = game.quad[w.i][w.j]
1103         tracktorpedo(w, step, number, nburst, iquad)
1104         if iquad == '.':
1105             continue
1106         # hit something
1107         setwnd(message_window)
1108         if not damaged(DSRSENS) or game.condition == "docked":
1109             skip(1)        # start new line after text track
1110         if iquad in ('E', 'F'): # Hit our ship
1111             skip(1)
1112             prout(_("Torpedo hits %s.") % crmshp())
1113             hit = 700.0 + randreal(100) - \
1114                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1115             newcnd() # we're blown out of dock
1116             if game.landed or game.condition == "docked":
1117                 return hit # Cheat if on a planet
1118             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1119             # is 143 degrees, which is almost exactly 4.8 clockface units
1120             displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
1121             displacement.nexttok()
1122             bumpto = displacement.sector()
1123             if not bumpto.valid_sector():
1124                 return hit
1125             if game.quad[bumpto.i][bumpto.j] == ' ':
1126                 finish(FHOLE)
1127                 return hit
1128             if game.quad[bumpto.i][bumpto.j] != '.':
1129                 # can't move into object
1130                 return hit
1131             game.sector = bumpto
1132             proutn(crmshp())
1133             game.quad[w.i][w.j] = '.'
1134             game.quad[bumpto.i][bumpto.j] = iquad
1135             prout(_(" displaced by blast to Sector %s ") % bumpto)
1136             for enemy in game.enemies:
1137                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1138             sortenemies()
1139             return None
1140         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy
1141             # find the enemy
1142             if iquad in ('C', 'S') and withprob(0.05):
1143                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1144                 prout(_("   torpedo neutralized."))
1145                 return None
1146             for enemy in game.enemies:
1147                 if w == enemy.location:
1148                     kp = math.fabs(enemy.power)
1149                     h1 = 700.0 + randrange(100) - \
1150                         1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1151                     h1 = math.fabs(h1)
1152                     if kp < h1:
1153                         h1 = kp
1154                     if enemy.power < 0:
1155                         enemy.power -= -h1
1156                     else:
1157                         enemy.power -= h1
1158                     if enemy.power == 0:
1159                         deadkl(w, iquad, w)
1160                         return None
1161                     proutn(crmena(True, iquad, "sector", w))
1162                     displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
1163                     displacement.nexttok()
1164                     bumpto = displacement.sector()
1165                     if not bumpto.valid_sector():
1166                         prout(_(" damaged but not destroyed."))
1167                         return
1168                     if game.quad[bumpto.i][bumpto.j] == ' ':
1169                         prout(_(" buffeted into black hole."))
1170                         deadkl(w, iquad, bumpto)
1171                     if game.quad[bumpto.i][bumpto.j] != '.':
1172                         prout(_(" damaged but not destroyed."))
1173                     else:
1174                         prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1175                         enemy.location = bumpto
1176                         game.quad[w.i][w.j] = '.'
1177                         game.quad[bumpto.i][bumpto.j] = iquad
1178                         for enemy in game.enemies:
1179                             enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1180                         sortenemies()
1181                     break
1182             else:
1183                 prout("Internal error, no enemy where expected!")
1184                 raise SystemExit(1)
1185             return None
1186         elif iquad == 'B': # Hit a base
1187             skip(1)
1188             prout(_("***STARBASE DESTROYED.."))
1189             game.state.baseq = [x for x in game.state.baseq if x != game.quadrant]
1190             game.quad[w.i][w.j] = '.'
1191             game.base.invalidate()
1192             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
1193             game.state.chart[game.quadrant.i][game.quadrant.j].starbase = False
1194             game.state.basekl += 1
1195             newcnd()
1196             return None
1197         elif iquad == 'P': # Hit a planet
1198             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1199             game.state.nplankl += 1
1200             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1201             game.iplnet.pclass = "destroyed"
1202             game.iplnet = None
1203             game.plnet.invalidate()
1204             game.quad[w.i][w.j] = '.'
1205             if game.landed:
1206                 # captain perishes on planet
1207                 finish(FDPLANET)
1208             return None
1209         elif iquad == '@': # Hit an inhabited world -- very bad!
1210             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1211             game.state.nworldkl += 1
1212             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1213             game.iplnet.pclass = "destroyed"
1214             game.iplnet = None
1215             game.plnet.invalidate()
1216             game.quad[w.i][w.j] = '.'
1217             if game.landed:
1218                 # captain perishes on planet
1219                 finish(FDPLANET)
1220             prout(_("The torpedo destroyed an inhabited planet."))
1221             return None
1222         elif iquad == '*': # Hit a star
1223             if withprob(0.9):
1224                 nova(w)
1225             else:
1226                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1227             return None
1228         elif iquad == '?': # Hit a thingy
1229             if not (game.options & OPTION_THINGY) or withprob(0.3):
1230                 skip(1)
1231                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1232                 skip(1)
1233                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1234                 skip(1)
1235                 proutn(_("Mr. Spock-"))
1236                 prouts(_("  \"Fascinating!\""))
1237                 skip(1)
1238                 deadkl(w, iquad, w)
1239             else:
1240                 # Stas Sergeev added the possibility that
1241                 # you can shove the Thingy and piss it off.
1242                 # It then becomes an enemy and may fire at you.
1243                 thing.angry()
1244             return None
1245         elif iquad == ' ': # Black hole
1246             skip(1)
1247             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1248             return None
1249         elif iquad == '#': # hit the web
1250             skip(1)
1251             prout(_("***Torpedo absorbed by Tholian web."))
1252             return None
1253         elif iquad == 'T':  # Hit a Tholian
1254             h1 = 700.0 + randrange(100) - \
1255                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1256             h1 = math.fabs(h1)
1257             if h1 >= 600:
1258                 game.quad[w.i][w.j] = '.'
1259                 deadkl(w, iquad, w)
1260                 game.tholian = None
1261                 return None
1262             skip(1)
1263             proutn(crmena(True, 'T', "sector", w))
1264             if withprob(0.05):
1265                 prout(_(" survives photon blast."))
1266                 return None
1267             prout(_(" disappears."))
1268             game.tholian.move(None)
1269             game.quad[w.i][w.j] = '#'
1270             dropin(' ')
1271             return None
1272         else: # Problem!
1273             skip(1)
1274             proutn("Don't know how to handle torpedo collision with ")
1275             proutn(crmena(True, iquad, "sector", w))
1276             skip(1)
1277             return None
1278         break
1279     skip(1)
1280     setwnd(message_window)
1281     prout(_("Torpedo missed."))
1282     return None
1283
1284 def fry(hit):
1285     "Critical-hit resolution."
1286     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1287         return
1288     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1289     proutn(_("***CRITICAL HIT--"))
1290     # Select devices and cause damage
1291     cdam = []
1292     while ncrit > 0:
1293         while True:
1294             j = randdevice()
1295             # Cheat to prevent shuttle damage unless on ship
1296             if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship")):
1297                 break
1298         cdam.append(j)
1299         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1300         game.damage[j] += extradm
1301         ncrit -= 1
1302     skipcount = 0
1303     for (i, j) in enumerate(cdam):
1304         proutn(device[j])
1305         if skipcount % 3 == 2 and i < len(cdam)-1:
1306             skip(1)
1307         skipcount += 1
1308         if i < len(cdam)-1:
1309             proutn(_(" and "))
1310     prout(_(" damaged."))
1311     if damaged(DSHIELD) and game.shldup:
1312         prout(_("***Shields knocked down."))
1313         game.shldup = False
1314
1315 def attack(torps_ok):
1316     # bad guy attacks us
1317     # torps_ok == False forces use of phasers in an attack
1318     # game could be over at this point, check
1319     if game.alldone:
1320         return
1321     attempt = False
1322     ihurt = False
1323     hitmax = 0.0
1324     hittot = 0.0
1325     chgfac = 1.0
1326     where = "neither"
1327     if game.idebug:
1328         prout("=== ATTACK!")
1329     # Tholian gets to move before attacking
1330     if game.tholian:
1331         movetholian()
1332     # if you have just entered the RNZ, you'll get a warning
1333     if game.neutz: # The one chance not to be attacked
1334         game.neutz = False
1335         return
1336     # commanders get a chance to tac-move towards you
1337     if (((game.quadrant in game.state.kcmdr or game.state.kscmdr == game.quadrant) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
1338         for (bugout, enemy, old, goto) in  moveklings():
1339             if bugout:
1340                 # we know about this if either short or long range
1341                 # sensors are working
1342                 if damaged(DSRSENS) and damaged(DLRSENS) \
1343                        and game.condition != "docked":
1344                     prout(crmena(True, enemy.type, "sector", old) + \
1345                           (_(" escapes to Quadrant %s (and regains strength).") % goto))
1346             else: # Enemy still in-sector
1347                 if enemy.move(goto):
1348                     if not damaged(DSRSENS) or game.condition == "docked":
1349                         proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
1350                         if enemy.kdist < old:
1351                             proutn(_(" advances to "))
1352                         else:
1353                             proutn(_(" retreats to "))
1354                         prout("Sector %s." % goto)
1355         sortenemies()
1356     # if no enemies remain after movement, we're done
1357     if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
1358         return
1359     # set up partial hits if attack happens during shield status change
1360     pfac = 1.0/game.inshld
1361     if game.shldchg:
1362         chgfac = 0.25 + randreal(0.5)
1363     skip(1)
1364     # message verbosity control
1365     if game.skill <= SKILL_FAIR:
1366         where = "sector"
1367     for enemy in game.enemies:
1368         if enemy.power < 0:
1369             continue        # too weak to attack
1370         # compute hit strength and diminish shield power
1371         r = randreal()
1372         # Increase chance of photon torpedos if docked or enemy energy is low
1373         if game.condition == "docked":
1374             r *= 0.25
1375         if enemy.power < 500:
1376             r *= 0.25
1377         if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
1378             continue
1379         # different enemies have different probabilities of throwing a torp
1380         usephasers = not torps_ok or \
1381             (enemy.type == 'K' and r > 0.0005) or \
1382             (enemy.type == 'C' and r > 0.015) or \
1383             (enemy.type == 'R' and r > 0.3) or \
1384             (enemy.type == 'S' and r > 0.07) or \
1385             (enemy.type == '?' and r > 0.05)
1386         if usephasers:            # Enemy uses phasers
1387             if game.condition == "docked":
1388                 continue # Don't waste the effort!
1389             attempt = True # Attempt to attack
1390             dustfac = randreal(0.8, 0.85)
1391             hit = enemy.power*math.pow(dustfac, enemy.kavgd)
1392             enemy.power *= 0.75
1393         else: # Enemy uses photon torpedo
1394             # We should be able to make the bearing() method work here
1395             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1396             hit = 0
1397             proutn(_("***TORPEDO INCOMING"))
1398             if not damaged(DSRSENS):
1399                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1400             attempt = True
1401             prout("  ")
1402             dispersion = (randreal()+randreal())*0.5 - 0.5
1403             dispersion += 0.002*enemy.power*dispersion
1404             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1405             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
1406                 finish(FWON) # Klingons did themselves in!
1407             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1408                 return # Supernova or finished
1409             if hit is None:
1410                 continue
1411         # incoming phaser or torpedo, shields may dissipate it
1412         if game.shldup or game.shldchg or game.condition == "docked":
1413             # shields will take hits
1414             propor = pfac * game.shield
1415             if game.condition == "docked":
1416                 propor *= 2.1
1417             if propor < 0.1:
1418                 propor = 0.1
1419             hitsh = propor*chgfac*hit+1.0
1420             absorb = 0.8*hitsh
1421             if absorb > game.shield:
1422                 absorb = game.shield
1423             game.shield -= absorb
1424             hit -= hitsh
1425             # taking a hit blasts us out of a starbase dock
1426             if game.condition == "docked":
1427                 dock(False)
1428             # but the shields may take care of it
1429             if propor > 0.1 and hit < 0.005*game.energy:
1430                 continue
1431         # hit from this opponent got through shields, so take damage
1432         ihurt = True
1433         proutn(_("%d unit hit") % int(hit))
1434         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1435             proutn(_(" on the ") + crmshp())
1436         if not damaged(DSRSENS) and usephasers:
1437             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1438         skip(1)
1439         # Decide if hit is critical
1440         if hit > hitmax:
1441             hitmax = hit
1442         hittot += hit
1443         fry(hit)
1444         game.energy -= hit
1445     if game.energy <= 0:
1446         # Returning home upon your shield, not with it...
1447         finish(FBATTLE)
1448         return
1449     if not attempt and game.condition == "docked":
1450         prout(_("***Enemies decide against attacking your ship."))
1451     percent = 100.0*pfac*game.shield+0.5
1452     if not ihurt:
1453         # Shields fully protect ship
1454         proutn(_("Enemy attack reduces shield strength to "))
1455     else:
1456         # Emit message if starship suffered hit(s)
1457         skip(1)
1458         proutn(_("Energy left %2d    shields ") % int(game.energy))
1459         if game.shldup:
1460             proutn(_("up "))
1461         elif not damaged(DSHIELD):
1462             proutn(_("down "))
1463         else:
1464             proutn(_("damaged, "))
1465     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1466     # Check if anyone was hurt
1467     if hitmax >= 200 or hittot >= 500:
1468         icas = randrange(int(hittot * 0.015))
1469         if icas >= 2:
1470             skip(1)
1471             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1472             prout(_("   in that last attack.\""))
1473             game.casual += icas
1474             game.state.crew -= icas
1475     # After attack, reset average distance to enemies
1476     for enemy in game.enemies:
1477         enemy.kavgd = enemy.kdist
1478     sortenemies()
1479     return
1480
1481 def deadkl(w, etype, mv):
1482     "Kill a Klingon, Tholian, Romulan, or Thingy."
1483     # Added mv to allow enemy to "move" before dying
1484     proutn(crmena(True, etype, "sector", mv))
1485     # Decide what kind of enemy it is and update appropriately
1486     if etype == 'R':
1487         # Chalk up a Romulan
1488         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1489         game.irhere -= 1
1490         game.state.nromrem -= 1
1491     elif etype == 'T':
1492         # Killed a Tholian
1493         game.tholian = None
1494     elif etype == '?':
1495         # Killed a Thingy
1496         global thing
1497         thing = None
1498     else:
1499         # Killed some type of Klingon
1500         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1501         game.klhere -= 1
1502         if type == 'C':
1503             game.state.kcmdr.remove(game.quadrant)
1504             unschedule(FTBEAM)
1505             if game.state.kcmdr:
1506                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1507             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1508                 unschedule(FCDBAS)
1509         elif type ==  'K':
1510             game.state.remkl -= 1
1511         elif type ==  'S':
1512             game.state.nscrem -= 1
1513             game.state.kscmdr.invalidate()
1514             game.isatb = 0
1515             game.iscate = False
1516             unschedule(FSCMOVE)
1517             unschedule(FSCDBAS)
1518     # For each kind of enemy, finish message to player
1519     prout(_(" destroyed."))
1520     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
1521         return
1522     game.recompute()
1523     # Remove enemy ship from arrays describing local conditions
1524     for e in game.enemies:
1525         if e.location == w:
1526             e.move(None)
1527             break
1528     return
1529
1530 def targetcheck(w):
1531     "Return None if target is invalid, otherwise return a course angle."
1532     if not w.valid_sector():
1533         huh()
1534         return None
1535     delta = Coord()
1536     # C code this was translated from is wacky -- why the sign reversal?
1537     delta.j = (w.j - game.sector.j)
1538     delta.i = (game.sector.i - w.i)
1539     if delta == Coord(0, 0):
1540         skip(1)
1541         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1542         prout(_("  I recommend an immediate review of"))
1543         prout(_("  the Captain's psychological profile.\""))
1544         scanner.chew()
1545         return None
1546     return delta.bearing()
1547
1548 def torps():
1549     "Launch photon torpedo salvo."
1550     tcourse = []
1551     game.ididit = False
1552     if damaged(DPHOTON):
1553         prout(_("Photon tubes damaged."))
1554         scanner.chew()
1555         return
1556     if game.torps == 0:
1557         prout(_("No torpedoes left."))
1558         scanner.chew()
1559         return
1560     # First, get torpedo count
1561     while True:
1562         scanner.nexttok()
1563         if scanner.token == "IHALPHA":
1564             huh()
1565             return
1566         elif scanner.token == "IHEOL" or not scanner.waiting():
1567             prout(_("%d torpedoes left.") % game.torps)
1568             scanner.chew()
1569             proutn(_("Number of torpedoes to fire- "))
1570             continue        # Go back around to get a number
1571         else: # key == "IHREAL"
1572             n = scanner.int()
1573             if n <= 0: # abort command
1574                 scanner.chew()
1575                 return
1576             if n > MAXBURST:
1577                 scanner.chew()
1578                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1579                 return
1580             if n > game.torps:
1581                 scanner.chew()        # User requested more torps than available
1582                 continue        # Go back around
1583             break        # All is good, go to next stage
1584     # Next, get targets
1585     target = []
1586     for i in range(n):
1587         key = scanner.nexttok()
1588         if i == 0 and key == "IHEOL":
1589             break        # no coordinate waiting, we will try prompting
1590         if i == 1 and key == "IHEOL":
1591             # direct all torpedoes at one target
1592             while i < n:
1593                 target.append(target[0])
1594                 tcourse.append(tcourse[0])
1595                 i += 1
1596             break
1597         scanner.push(scanner.token)
1598         target.append(scanner.getcoord())
1599         if target[-1] is None:
1600             return
1601         tcourse.append(targetcheck(target[-1]))
1602         if tcourse[-1] is None:
1603             return
1604     scanner.chew()
1605     if len(target) == 0:
1606         # prompt for each one
1607         for i in range(n):
1608             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1609             scanner.chew()
1610             target.append(scanner.getcoord())
1611             if target[-1] is None:
1612                 return
1613             tcourse.append(targetcheck(target[-1]))
1614             if tcourse[-1] is None:
1615                 return
1616     game.ididit = True
1617     # Loop for moving <n> torpedoes
1618     for i in range(n):
1619         if game.condition != "docked":
1620             game.torps -= 1
1621         dispersion = (randreal()+randreal())*0.5 -0.5
1622         if math.fabs(dispersion) >= 0.47:
1623             # misfire!
1624             dispersion *= randreal(1.2, 2.2)
1625             if n > 0:
1626                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1627             else:
1628                 prouts(_("***TORPEDO MISFIRES."))
1629             skip(1)
1630             if i < n:
1631                 prout(_("  Remainder of burst aborted."))
1632             if withprob(0.2):
1633                 prout(_("***Photon tubes damaged by misfire."))
1634                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1635             break
1636         if game.shldup or game.condition == "docked":
1637             dispersion *= 1.0 + 0.0001*game.shield
1638         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1639         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1640             return
1641     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
1642         finish(FWON)
1643
1644 def overheat(rpow):
1645     "Check for phasers overheating."
1646     if rpow > 1500:
1647         checkburn = (rpow-1500.0)*0.00038
1648         if withprob(checkburn):
1649             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1650             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1651
1652 def checkshctrl(rpow):
1653     "Check shield control."
1654     skip(1)
1655     if withprob(0.998):
1656         prout(_("Shields lowered."))
1657         return False
1658     # Something bad has happened
1659     prouts(_("***RED ALERT!  RED ALERT!"))
1660     skip(2)
1661     hit = rpow*game.shield/game.inshld
1662     game.energy -= rpow+hit*0.8
1663     game.shield -= hit*0.2
1664     if game.energy <= 0.0:
1665         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1666         skip(1)
1667         stars()
1668         finish(FPHASER)
1669         return True
1670     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1671     skip(2)
1672     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1673     icas = randrange(int(hit*0.012))
1674     skip(1)
1675     fry(0.8*hit)
1676     if icas:
1677         skip(1)
1678         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1679         prout(_("  %d casualties so far.\"") % icas)
1680         game.casual += icas
1681         game.state.crew -= icas
1682     skip(1)
1683     prout(_("Phaser energy dispersed by shields."))
1684     prout(_("Enemy unaffected."))
1685     overheat(rpow)
1686     return True
1687
1688 def hittem(hits):
1689     "Register a phaser hit on Klingons and Romulans."
1690     w = Coord()
1691     skip(1)
1692     kk = 0
1693     for wham in hits:
1694         if wham == 0:
1695             continue
1696         dustfac = randreal(0.9, 1.0)
1697         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1698         kpini = game.enemies[kk].power
1699         kp = math.fabs(kpini)
1700         if PHASEFAC*hit < kp:
1701             kp = PHASEFAC*hit
1702         if game.enemies[kk].power < 0:
1703             game.enemies[kk].power -= -kp
1704         else:
1705             game.enemies[kk].power -= kp
1706         kpow = game.enemies[kk].power
1707         w = game.enemies[kk].location
1708         if hit > 0.005:
1709             if not damaged(DSRSENS):
1710                 boom(w)
1711             proutn(_("%d unit hit on ") % int(hit))
1712         else:
1713             proutn(_("Very small hit on "))
1714         ienm = game.quad[w.i][w.j]
1715         if ienm == '?':
1716             thing.angry()
1717         proutn(crmena(False, ienm, "sector", w))
1718         skip(1)
1719         if kpow == 0:
1720             deadkl(w, ienm, w)
1721             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1722                 finish(FWON)
1723             if game.alldone:
1724                 return
1725             kk -= 1        # don't do the increment
1726             continue
1727         else: # decide whether or not to emasculate klingon
1728             if kpow > 0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1729                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1730                 prout(_("   has just lost its firepower.\""))
1731                 game.enemies[kk].power = -kpow
1732         kk += 1
1733     return
1734
1735 def phasers():
1736     "Fire phasers at bad guys."
1737     hits = []
1738     kz = 0
1739     k = 1
1740     irec = 0 # Cheating inhibitor
1741     ifast = False
1742     no = False
1743     itarg = True
1744     msgflag = True
1745     rpow = 0.0
1746     automode = "NOTSET"
1747     key = ""
1748     skip(1)
1749     # SR sensors and Computer are needed for automode
1750     if damaged(DSRSENS) or damaged(DCOMPTR):
1751         itarg = False
1752     if game.condition == "docked":
1753         prout(_("Phasers can't be fired through base shields."))
1754         scanner.chew()
1755         return
1756     if damaged(DPHASER):
1757         prout(_("Phaser control damaged."))
1758         scanner.chew()
1759         return
1760     if game.shldup:
1761         if damaged(DSHCTRL):
1762             prout(_("High speed shield control damaged."))
1763             scanner.chew()
1764             return
1765         if game.energy <= 200.0:
1766             prout(_("Insufficient energy to activate high-speed shield control."))
1767             scanner.chew()
1768             return
1769         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1770         ifast = True
1771     # Original code so convoluted, I re-did it all
1772     # (That was Tom Almy talking about the C code, I think -- ESR)
1773     while automode == "NOTSET":
1774         key = scanner.nexttok()
1775         if key == "IHALPHA":
1776             if scanner.sees("manual"):
1777                 if len(game.enemies)==0:
1778                     prout(_("There is no enemy present to select."))
1779                     scanner.chew()
1780                     key = "IHEOL"
1781                     automode = "AUTOMATIC"
1782                 else:
1783                     automode = "MANUAL"
1784                     key = scanner.nexttok()
1785             elif scanner.sees("automatic"):
1786                 if (not itarg) and len(game.enemies) != 0:
1787                     automode = "FORCEMAN"
1788                 else:
1789                     if len(game.enemies)==0:
1790                         prout(_("Energy will be expended into space."))
1791                     automode = "AUTOMATIC"
1792                     key = scanner.nexttok()
1793             elif scanner.sees("no"):
1794                 no = True
1795             else:
1796                 huh()
1797                 return
1798         elif key == "IHREAL":
1799             if len(game.enemies)==0:
1800                 prout(_("Energy will be expended into space."))
1801                 automode = "AUTOMATIC"
1802             elif not itarg:
1803                 automode = "FORCEMAN"
1804             else:
1805                 automode = "AUTOMATIC"
1806         else:
1807             # "IHEOL"
1808             if len(game.enemies)==0:
1809                 prout(_("Energy will be expended into space."))
1810                 automode = "AUTOMATIC"
1811             elif not itarg:
1812                 automode = "FORCEMAN"
1813             else:
1814                 proutn(_("Manual or automatic? "))
1815                 scanner.chew()
1816     avail = game.energy
1817     if ifast:
1818         avail -= 200.0
1819     if automode == "AUTOMATIC":
1820         if key == "IHALPHA" and scanner.sees("no"):
1821             no = True
1822             key = scanner.nexttok()
1823         if key != "IHREAL" and len(game.enemies) != 0:
1824             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1825         irec = 0
1826         while True:
1827             scanner.chew()
1828             if not kz:
1829                 for i in range(len(game.enemies)):
1830                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1831             kz = 1
1832             proutn(_("%d units required. ") % irec)
1833             scanner.chew()
1834             proutn(_("Units to fire= "))
1835             key = scanner.nexttok()
1836             if key != "IHREAL":
1837                 return
1838             rpow = scanner.real
1839             if rpow > avail:
1840                 proutn(_("Energy available= %.2f") % avail)
1841                 skip(1)
1842                 key = "IHEOL"
1843             if not rpow > avail:
1844                 break
1845         if rpow <= 0:
1846             # chicken out
1847             scanner.chew()
1848             return
1849         key = scanner.nexttok()
1850         if key == "IHALPHA" and scanner.sees("no"):
1851             no = True
1852         if ifast:
1853             game.energy -= 200 # Go and do it!
1854             if checkshctrl(rpow):
1855                 return
1856         scanner.chew()
1857         game.energy -= rpow
1858         extra = rpow
1859         if len(game.enemies):
1860             extra = 0.0
1861             powrem = rpow
1862             for i in range(len(game.enemies)):
1863                 hits.append(0.0)
1864                 if powrem <= 0:
1865                     continue
1866                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
1867                 over = randreal(1.01, 1.06) * hits[i]
1868                 temp = powrem
1869                 powrem -= hits[i] + over
1870                 if powrem <= 0 and temp < hits[i]:
1871                     hits[i] = temp
1872                 if powrem <= 0:
1873                     over = 0.0
1874                 extra += over
1875             if powrem > 0.0:
1876                 extra += powrem
1877             hittem(hits)
1878             game.ididit = True
1879         if extra > 0 and not game.alldone:
1880             if game.tholian:
1881                 proutn(_("*** Tholian web absorbs "))
1882                 if len(game.enemies)>0:
1883                     proutn(_("excess "))
1884                 prout(_("phaser energy."))
1885             else:
1886                 prout(_("%d expended on empty space.") % int(extra))
1887     elif automode == "FORCEMAN":
1888         scanner.chew()
1889         key = "IHEOL"
1890         if damaged(DCOMPTR):
1891             prout(_("Battle computer damaged, manual fire only."))
1892         else:
1893             skip(1)
1894             prouts(_("---WORKING---"))
1895             skip(1)
1896             prout(_("Short-range-sensors-damaged"))
1897             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1898             prout(_("Manual-fire-must-be-used"))
1899             skip(1)
1900     elif automode == "MANUAL":
1901         rpow = 0.0
1902         for k in range(len(game.enemies)):
1903             aim = game.enemies[k].location
1904             ienm = game.quad[aim.i][aim.j]
1905             if msgflag:
1906                 proutn(_("Energy available= %.2f") % (avail-0.006))
1907                 skip(1)
1908                 msgflag = False
1909                 rpow = 0.0
1910             if damaged(DSRSENS) and \
1911                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
1912                 prout(cramen(ienm) + _(" can't be located without short range scan."))
1913                 scanner.chew()
1914                 key = "IHEOL"
1915                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
1916                 k += 1
1917                 continue
1918             if key == "IHEOL":
1919                 scanner.chew()
1920                 if itarg and k > kz:
1921                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        randreal(1.01, 1.06) + 1.0
1922                 kz = k
1923                 proutn("(")
1924                 if not damaged(DCOMPTR):
1925                     proutn("%d" % irec)
1926                 else:
1927                     proutn("??")
1928                 proutn(")  ")
1929                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
1930                 key = scanner.nexttok()
1931             if key == "IHALPHA" and scanner.sees("no"):
1932                 no = True
1933                 key = scanner.nexttok()
1934                 continue
1935             if key == "IHALPHA":
1936                 huh()
1937                 return
1938             if key == "IHEOL":
1939                 if k == 1: # Let me say I'm baffled by this
1940                     msgflag = True
1941                 continue
1942             if scanner.real < 0:
1943                 # abort out
1944                 scanner.chew()
1945                 return
1946             hits[k] = scanner.real
1947             rpow += scanner.real
1948             # If total requested is too much, inform and start over
1949             if rpow > avail:
1950                 prout(_("Available energy exceeded -- try again."))
1951                 scanner.chew()
1952                 return
1953             key = scanner.nexttok() # scan for next value
1954             k += 1
1955         if rpow == 0.0:
1956             # zero energy -- abort
1957             scanner.chew()
1958             return
1959         if key == "IHALPHA" and scanner.sees("no"):
1960             no = True
1961         game.energy -= rpow
1962         scanner.chew()
1963         if ifast:
1964             game.energy -= 200.0
1965             if checkshctrl(rpow):
1966                 return
1967         hittem(hits)
1968         game.ididit = True
1969      # Say shield raised or malfunction, if necessary
1970     if game.alldone:
1971         return
1972     if ifast:
1973         skip(1)
1974         if no == 0:
1975             if withprob(0.01):
1976                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
1977                 prouts(_("         CLICK   CLICK   POP  . . ."))
1978                 prout(_(" No response, sir!"))
1979                 game.shldup = False
1980             else:
1981                 prout(_("Shields raised."))
1982         else:
1983             game.shldup = False
1984     overheat(rpow)
1985
1986 # Code from events,c begins here.
1987
1988 # This isn't a real event queue a la BSD Trek yet -- you can only have one
1989 # event of each type active at any given time.  Mostly these means we can
1990 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
1991 # BSD Trek, from which we swiped the idea, can have up to 5.
1992
1993 def unschedule(evtype):
1994     "Remove an event from the schedule."
1995     game.future[evtype].date = FOREVER
1996     return game.future[evtype]
1997
1998 def is_scheduled(evtype):
1999     "Is an event of specified type scheduled."
2000     return game.future[evtype].date != FOREVER
2001
2002 def scheduled(evtype):
2003     "When will this event happen?"
2004     return game.future[evtype].date
2005
2006 def schedule(evtype, offset):
2007     "Schedule an event of specified type."
2008     game.future[evtype].date = game.state.date + offset
2009     return game.future[evtype]
2010
2011 def postpone(evtype, offset):
2012     "Postpone a scheduled event."
2013     game.future[evtype].date += offset
2014
2015 def cancelrest():
2016     "Rest period is interrupted by event."
2017     if game.resting:
2018         skip(1)
2019         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2020         if ja():
2021             game.resting = False
2022             game.optime = 0.0
2023             return True
2024     return False
2025
2026 def events():
2027     "Run through the event queue looking for things to do."
2028     i = 0
2029     fintim = game.state.date + game.optime
2030     yank = 0
2031     ictbeam = False
2032     istract = False
2033     w = Coord()
2034     hold = Coord()
2035     ev = Event()
2036     ev2 = Event()
2037
2038     def tractorbeam(yank):
2039         "Tractor-beaming cases merge here."
2040         announce()
2041         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2042         skip(1)
2043         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2044         # If Kirk & Co. screwing around on planet, handle
2045         atover(True) # atover(true) is Grab
2046         if game.alldone:
2047             return
2048         if game.icraft: # Caught in Galileo?
2049             finish(FSTRACTOR)
2050             return
2051         # Check to see if shuttle is aboard
2052         if game.iscraft == "offship":
2053             skip(1)
2054             if withprob(0.5):
2055                 prout(_("Galileo, left on the planet surface, is captured"))
2056                 prout(_("by aliens and made into a flying McDonald's."))
2057                 game.damage[DSHUTTL] = -10
2058                 game.iscraft = "removed"
2059             else:
2060                 prout(_("Galileo, left on the planet surface, is well hidden."))
2061         if evcode == FSPY:
2062             game.quadrant = game.state.kscmdr
2063         else:
2064             game.quadrant = game.state.kcmdr[i]
2065         game.sector = randplace(QUADSIZE)
2066         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2067                % (game.quadrant, game.sector))
2068         if game.resting:
2069             prout(_("(Remainder of rest/repair period cancelled.)"))
2070             game.resting = False
2071         if not game.shldup:
2072             if not damaged(DSHIELD) and game.shield > 0:
2073                 doshield(shraise=True) # raise shields
2074                 game.shldchg = False
2075             else:
2076                 prout(_("(Shields not currently useable.)"))
2077         newqad()
2078         # Adjust finish time to time of tractor beaming?
2079         # fintim = game.state.date+game.optime
2080         attack(torps_ok=False)
2081         if not game.state.kcmdr:
2082             unschedule(FTBEAM)
2083         else:
2084             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2085
2086     def destroybase():
2087         "Code merges here for any commander destroying a starbase."
2088         # Not perfect, but will have to do
2089         # Handle case where base is in same quadrant as starship
2090         if game.battle == game.quadrant:
2091             game.state.chart[game.battle.i][game.battle.j].starbase = False
2092             game.quad[game.base.i][game.base.j] = '.'
2093             game.base.invalidate()
2094             newcnd()
2095             skip(1)
2096             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2097         elif game.state.baseq and communicating():
2098             # Get word via subspace radio
2099             announce()
2100             skip(1)
2101             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2102             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2103             if game.isatb == 2:
2104                 prout(_("the Klingon Super-Commander"))
2105             else:
2106                 prout(_("a Klingon Commander"))
2107             game.state.chart[game.battle.i][game.battle.j].starbase = False
2108         # Remove Starbase from galaxy
2109         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2110         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2111         if game.isatb == 2:
2112             # reinstate a commander's base attack
2113             game.battle = hold
2114             game.isatb = 0
2115         else:
2116             game.battle.invalidate()
2117     if game.idebug:
2118         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2119         for i in range(1, NEVENTS):
2120             if   i == FSNOVA:  proutn("=== Supernova       ")
2121             elif i == FTBEAM:  proutn("=== T Beam          ")
2122             elif i == FSNAP:   proutn("=== Snapshot        ")
2123             elif i == FBATTAK: proutn("=== Base Attack     ")
2124             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2125             elif i == FSCMOVE: proutn("=== SC Move         ")
2126             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2127             elif i == FDSPROB: proutn("=== Probe Move      ")
2128             elif i == FDISTR:  proutn("=== Distress Call   ")
2129             elif i == FENSLV:  proutn("=== Enslavement     ")
2130             elif i == FREPRO:  proutn("=== Klingon Build   ")
2131             if is_scheduled(i):
2132                 prout("%.2f" % (scheduled(i)))
2133             else:
2134                 prout("never")
2135     radio_was_broken = damaged(DRADIO)
2136     hold.i = hold.j = 0
2137     while True:
2138         # Select earliest extraneous event, evcode==0 if no events
2139         evcode = FSPY
2140         if game.alldone:
2141             return
2142         datemin = fintim
2143         for l in range(1, NEVENTS):
2144             if game.future[l].date < datemin:
2145                 evcode = l
2146                 if game.idebug:
2147                     prout("== Event %d fires" % evcode)
2148                 datemin = game.future[l].date
2149         xtime = datemin-game.state.date
2150         game.state.date = datemin
2151         # Decrement Federation resources and recompute remaining time
2152         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2153         game.recompute()
2154         if game.state.remtime <= 0:
2155             finish(FDEPLETE)
2156             return
2157         # Any crew left alive?
2158         if game.state.crew <= 0:
2159             finish(FCREW)
2160             return
2161         # Is life support adequate?
2162         if damaged(DLIFSUP) and game.condition != "docked":
2163             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2164                 finish(FLIFESUP)
2165                 return
2166             game.lsupres -= xtime
2167             if game.damage[DLIFSUP] <= xtime:
2168                 game.lsupres = game.inlsr
2169         # Fix devices
2170         repair = xtime
2171         if game.condition == "docked":
2172             repair /= DOCKFAC
2173         # Don't fix Deathray here
2174         for l in range(NDEVICES):
2175             if game.damage[l] > 0.0 and l != DDRAY:
2176                 if game.damage[l]-repair > 0.0:
2177                     game.damage[l] -= repair
2178                 else:
2179                     game.damage[l] = 0.0
2180         # If radio repaired, update star chart and attack reports
2181         if radio_was_broken and not damaged(DRADIO):
2182             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2183             prout(_("   surveillance reports are coming in."))
2184             skip(1)
2185             if not game.iseenit:
2186                 attackreport(False)
2187                 game.iseenit = True
2188             rechart()
2189             prout(_("   The star chart is now up to date.\""))
2190             skip(1)
2191         # Cause extraneous event EVCODE to occur
2192         game.optime -= xtime
2193         if evcode == FSNOVA: # Supernova
2194             announce()
2195             supernova(None)
2196             schedule(FSNOVA, expran(0.5*game.intime))
2197             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2198                 return
2199         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2200             if game.state.nscrem == 0 or \
2201                 ictbeam or istract or \
2202                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2203                 return
2204             if game.ientesc or \
2205                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2206                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2207                 (damaged(DSHIELD) and \
2208                  (game.energy < 2500 or damaged(DPHASER)) and \
2209                  (game.torps < 5 or damaged(DPHOTON))):
2210                 # Tractor-beam her!
2211                 istract = ictbeam = True
2212                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2213             else:
2214                 return
2215         elif evcode == FTBEAM: # Tractor beam
2216             if not game.state.kcmdr:
2217                 unschedule(FTBEAM)
2218                 continue
2219             i = randrange(len(game.state.kcmdr))
2220             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2221             if istract or game.condition == "docked" or yank == 0:
2222                 # Drats! Have to reschedule
2223                 schedule(FTBEAM,
2224                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2225                 continue
2226             ictbeam = True
2227             tractorbeam(yank)
2228         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2229             game.snapsht = copy.deepcopy(game.state)
2230             game.state.snap = True
2231             schedule(FSNAP, expran(0.5 * game.intime))
2232         elif evcode == FBATTAK: # Commander attacks starbase
2233             if not game.state.kcmdr or not game.state.baseq:
2234                 # no can do
2235                 unschedule(FBATTAK)
2236                 unschedule(FCDBAS)
2237                 continue
2238             ibq = None  # Force battle location to persist past loop
2239             try:
2240                 for ibq in game.state.baseq:
2241                     for cmdr in game.state.kcmdr:
2242                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2243                             raise JumpOut
2244                 # no match found -- try later
2245                 schedule(FBATTAK, expran(0.3*game.intime))
2246                 unschedule(FCDBAS)
2247                 continue
2248             except JumpOut:
2249                 pass
2250             # commander + starbase combination found -- launch attack
2251             game.battle = ibq
2252             schedule(FCDBAS, randreal(1.0, 4.0))
2253             if game.isatb: # extra time if SC already attacking
2254                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2255             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2256             game.iseenit = False
2257             if not communicating():
2258                 continue # No warning :-(
2259             game.iseenit = True
2260             announce()
2261             skip(1)
2262             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2263             prout(_("   reports that it is under attack and that it can"))
2264             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2265             if cancelrest():
2266                 return
2267         elif evcode == FSCDBAS: # Supercommander destroys base
2268             unschedule(FSCDBAS)
2269             game.isatb = 2
2270             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2271                 continue # WAS RETURN!
2272             hold = game.battle
2273             game.battle = game.state.kscmdr
2274             destroybase()
2275         elif evcode == FCDBAS: # Commander succeeds in destroying base
2276             if evcode == FCDBAS:
2277                 unschedule(FCDBAS)
2278                 if not game.state.baseq() \
2279                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2280                     game.battle.invalidate()
2281                     continue
2282                 # find the lucky pair
2283                 for cmdr in game.state.kcmdr:
2284                     if cmdr == game.battle:
2285                         break
2286                 else:
2287                     # No action to take after all
2288                     continue
2289             destroybase()
2290         elif evcode == FSCMOVE: # Supercommander moves
2291             schedule(FSCMOVE, 0.2777)
2292             if not game.ientesc and not istract and game.isatb != 1 and \
2293                    (not game.iscate or not game.justin):
2294                 supercommander()
2295         elif evcode == FDSPROB: # Move deep space probe
2296             schedule(FDSPROB, 0.01)
2297             if not game.probe.nexttok():
2298                 if not game.probe.quadrant().valid_quadrant() or \
2299                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2300                     # Left galaxy or ran into supernova
2301                     if communicating():
2302                         announce()
2303                         skip(1)
2304                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2305                         if not game.probe.quadrant().valid_quadrant():
2306                             prout(_("has left the galaxy.\""))
2307                         else:
2308                             prout(_("is no longer transmitting.\""))
2309                     unschedule(FDSPROB)
2310                     continue
2311                 if communicating():
2312                     #announce()
2313                     skip(1)
2314                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2315             pquad = game.probe.quadrant()
2316             pdest = game.state.galaxy[pquad.i][pquad.j]
2317             if communicating():
2318                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2319                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2320                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2321                 pdest.charted = True
2322             game.probe.moves -= 1 # One less to travel
2323             if game.probe.arrived() and game.isarmed and pdest.stars:
2324                 supernova(game.probe)                # fire in the hole!
2325                 unschedule(FDSPROB)
2326                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2327                     return
2328         elif evcode == FDISTR: # inhabited system issues distress call
2329             unschedule(FDISTR)
2330             # try a whole bunch of times to find something suitable
2331             for i in range(100):
2332                 # need a quadrant which is not the current one,
2333                 # which has some stars which are inhabited and
2334                 # not already under attack, which is not
2335                 # supernova'ed, and which has some Klingons in it
2336                 w = randplace(GALSIZE)
2337                 q = game.state.galaxy[w.i][w.j]
2338                 if not (game.quadrant == w or q.planet is None or \
2339                       not q.planet.inhabited or \
2340                       q.supernova or q.status!="secure" or q.klingons<=0):
2341                     break
2342             else:
2343                 # can't seem to find one; ignore this call
2344                 if game.idebug:
2345                     prout("=== Couldn't find location for distress event.")
2346                 continue
2347             # got one!!  Schedule its enslavement
2348             ev = schedule(FENSLV, expran(game.intime))
2349             ev.quadrant = w
2350             q.status = "distressed"
2351             # tell the captain about it if we can
2352             if communicating():
2353                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2354                         % (q.planet, repr(w)))
2355                 prout(_("by a Klingon invasion fleet."))
2356                 if cancelrest():
2357                     return
2358         elif evcode == FENSLV:                # starsystem is enslaved
2359             ev = unschedule(FENSLV)
2360             # see if current distress call still active
2361             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2362             if q.klingons <= 0:
2363                 q.status = "secure"
2364                 continue
2365             q.status = "enslaved"
2366
2367             # play stork and schedule the first baby
2368             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2369             ev2.quadrant = ev.quadrant
2370
2371             # report the disaster if we can
2372             if communicating():
2373                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2374                         q.planet)
2375                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2376         elif evcode == FREPRO:                # Klingon reproduces
2377             # If we ever switch to a real event queue, we'll need to
2378             # explicitly retrieve and restore the x and y.
2379             ev = schedule(FREPRO, expran(1.0 * game.intime))
2380             # see if current distress call still active
2381             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2382             if q.klingons <= 0:
2383                 q.status = "secure"
2384                 continue
2385             if game.state.remkl >= MAXKLGAME:
2386                 continue                # full right now
2387             # reproduce one Klingon
2388             w = ev.quadrant
2389             m = Coord()
2390             if game.klhere >= MAXKLQUAD:
2391                 try:
2392                     # this quadrant not ok, pick an adjacent one
2393                     for m.i in range(w.i - 1, w.i + 2):
2394                         for m.j in range(w.j - 1, w.j + 2):
2395                             if not m.valid_quadrant():
2396                                 continue
2397                             q = game.state.galaxy[m.i][m.j]
2398                             # check for this quad ok (not full & no snova)
2399                             if q.klingons >= MAXKLQUAD or q.supernova:
2400                                 continue
2401                             raise JumpOut
2402                     # search for eligible quadrant failed
2403                     continue
2404                 except JumpOut:
2405                     w = m
2406             # deliver the child
2407             game.state.remkl += 1
2408             q.klingons += 1
2409             if game.quadrant == w:
2410                 game.klhere += 1
2411                 game.enemies.append(newkling())
2412             # recompute time left
2413             game.recompute()
2414             if communicating():
2415                 if game.quadrant == w:
2416                     prout(_("Spock- sensors indicate the Klingons have"))
2417                     prout(_("launched a warship from %s.") % q.planet)
2418                 else:
2419                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2420                     if q.planet != None:
2421                         proutn(_("near %s ") % q.planet)
2422                     prout(_("in Quadrant %s.") % w)
2423
2424 def wait():
2425     "Wait on events."
2426     game.ididit = False
2427     while True:
2428         key = scanner.nexttok()
2429         if key  != "IHEOL":
2430             break
2431         proutn(_("How long? "))
2432     scanner.chew()
2433     if key != "IHREAL":
2434         huh()
2435         return
2436     origTime = delay = scanner.real
2437     if delay <= 0.0:
2438         return
2439     if delay >= game.state.remtime or len(game.enemies) != 0:
2440         proutn(_("Are you sure? "))
2441         if not ja():
2442             return
2443     # Alternate resting periods (events) with attacks
2444     game.resting = True
2445     while True:
2446         if delay <= 0:
2447             game.resting = False
2448         if not game.resting:
2449             prout(_("%d stardates left.") % int(game.state.remtime))
2450             return
2451         temp = game.optime = delay
2452         if len(game.enemies):
2453             rtime = randreal(1.0, 2.0)
2454             if rtime < temp:
2455                 temp = rtime
2456             game.optime = temp
2457         if game.optime < delay:
2458             attack(torps_ok=False)
2459         if game.alldone:
2460             return
2461         events()
2462         game.ididit = True
2463         if game.alldone:
2464             return
2465         delay -= temp
2466         # Repair Deathray if long rest at starbase
2467         if origTime-delay >= 9.99 and game.condition == "docked":
2468             game.damage[DDRAY] = 0.0
2469         # leave if quadrant supernovas
2470         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2471             break
2472     game.resting = False
2473     game.optime = 0.0
2474
2475 def nova(nov):
2476     "Star goes nova."
2477     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2478     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2479     if withprob(0.05):
2480         # Wow! We've supernova'ed
2481         supernova(game.quadrant)
2482         return
2483     # handle initial nova
2484     game.quad[nov.i][nov.j] = '.'
2485     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2486     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2487     game.state.starkl += 1
2488     # Set up queue to recursively trigger adjacent stars
2489     hits = [nov]
2490     kount = 0
2491     while hits:
2492         offset = Coord()
2493         start = hits.pop()
2494         for offset.i in range(-1, 1+1):
2495             for offset.j in range(-1, 1+1):
2496                 if offset.j == 0 and offset.i == 0:
2497                     continue
2498                 neighbor = start + offset
2499                 if not neighbor.valid_sector():
2500                     continue
2501                 iquad = game.quad[neighbor.i][neighbor.j]
2502                 # Empty space ends reaction
2503                 if iquad in ('.', '?', ' ', 'T', '#'):
2504                     pass
2505                 elif iquad == '*': # Affect another star
2506                     if withprob(0.05):
2507                         # This star supernovas
2508                         supernova(game.quadrant)
2509                         return
2510                     else:
2511                         hits.append(neighbor)
2512                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2513                         game.state.starkl += 1
2514                         proutn(crmena(True, '*', "sector", neighbor))
2515                         prout(_(" novas."))
2516                         game.quad[neighbor.i][neighbor.j] = '.'
2517                         kount += 1
2518                 elif iquad in ('P', '@'): # Destroy planet
2519                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2520                     if iquad == 'P':
2521                         game.state.nplankl += 1
2522                     else:
2523                         game.state.nworldkl += 1
2524                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2525                     game.iplnet.pclass = "destroyed"
2526                     game.iplnet = None
2527                     game.plnet.invalidate()
2528                     if game.landed:
2529                         finish(FPNOVA)
2530                         return
2531                     game.quad[neighbor.i][neighbor.j] = '.'
2532                 elif iquad == 'B': # Destroy base
2533                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2534                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2535                     game.base.invalidate()
2536                     game.state.basekl += 1
2537                     newcnd()
2538                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2539                     game.quad[neighbor.i][neighbor.j] = '.'
2540                 elif iquad in ('E', 'F'): # Buffet ship
2541                     prout(_("***Starship buffeted by nova."))
2542                     if game.shldup:
2543                         if game.shield >= 2000.0:
2544                             game.shield -= 2000.0
2545                         else:
2546                             diff = 2000.0 - game.shield
2547                             game.energy -= diff
2548                             game.shield = 0.0
2549                             game.shldup = False
2550                             prout(_("***Shields knocked out."))
2551                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2552                     else:
2553                         game.energy -= 2000.0
2554                     if game.energy <= 0:
2555                         finish(FNOVA)
2556                         return
2557                     # add in course nova contributes to kicking starship
2558                     bump += (game.sector-hits[-1]).sgn()
2559                 elif iquad == 'K': # kill klingon
2560                     deadkl(neighbor, iquad, neighbor)
2561                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2562                     target = None
2563                     for ll in range(len(game.enemies)):
2564                         if game.enemies[ll].location == neighbor:
2565                             target = game.enemies[ll]
2566                             break
2567                     if target is not None:
2568                         target.power -= 800.0 # If firepower is lost, die
2569                         if target.power <= 0.0:
2570                             deadkl(neighbor, iquad, neighbor)
2571                             continue    # neighbor loop
2572                         # Else enemy gets flung by the blast wave
2573                         newc = neighbor + neighbor - hits[-1]
2574                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2575                         if not newc.valid_sector():
2576                             # can't leave quadrant
2577                             skip(1)
2578                             continue
2579                         iquad1 = game.quad[newc.i][newc.j]
2580                         if iquad1 == ' ':
2581                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2582                             skip(1)
2583                             deadkl(neighbor, iquad, newc)
2584                             continue
2585                         if iquad1 != '.':
2586                             # can't move into something else
2587                             skip(1)
2588                             continue
2589                         proutn(_(", buffeted to Sector %s") % newc)
2590                         game.quad[neighbor.i][neighbor.j] = '.'
2591                         game.quad[newc.i][newc.j] = iquad
2592                         target.move(newc)
2593     # Starship affected by nova -- kick it away.
2594     dist = kount*0.1
2595     direc = ncourse[3*(bump.i+1)+bump.j+2]
2596     if direc == 0.0:
2597         dist = 0.0
2598     if dist == 0.0:
2599         return
2600     scourse = course(bearing=direc, distance=dist)
2601     game.optime = scourse.time(w=4)
2602     skip(1)
2603     prout(_("Force of nova displaces starship."))
2604     imove(scourse, noattack=True)
2605     game.optime = scourse.time(w=4)
2606     return
2607
2608 def supernova(w):
2609     "Star goes supernova."
2610     num = 0; npdead = 0
2611     if w != None:
2612         nq = copy.copy(w)
2613     else:
2614         # Scheduled supernova -- select star at random.
2615         nstars = 0
2616         nq = Coord()
2617         for nq.i in range(GALSIZE):
2618             for nq.j in range(GALSIZE):
2619                 nstars += game.state.galaxy[nq.i][nq.j].stars
2620         if stars == 0:
2621             return # nothing to supernova exists
2622         num = randrange(nstars) + 1
2623         for nq.i in range(GALSIZE):
2624             for nq.j in range(GALSIZE):
2625                 num -= game.state.galaxy[nq.i][nq.j].stars
2626                 if num <= 0:
2627                     break
2628             if num <=0:
2629                 break
2630         if game.idebug:
2631             proutn("=== Super nova here?")
2632             if ja():
2633                 nq = game.quadrant
2634     if not nq == game.quadrant or game.justin:
2635         # it isn't here, or we just entered (treat as enroute)
2636         if communicating():
2637             skip(1)
2638             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2639             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2640     else:
2641         ns = Coord()
2642         # we are in the quadrant!
2643         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2644         for ns.i in range(QUADSIZE):
2645             for ns.j in range(QUADSIZE):
2646                 if game.quad[ns.i][ns.j]=='*':
2647                     num -= 1
2648                     if num==0:
2649                         break
2650             if num==0:
2651                 break
2652         skip(1)
2653         prouts(_("***RED ALERT!  RED ALERT!"))
2654         skip(1)
2655         prout(_("***Incipient supernova detected at Sector %s") % ns)
2656         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2657             proutn(_("Emergency override attempts t"))
2658             prouts("***************")
2659             skip(1)
2660             stars()
2661             game.alldone = True
2662     # destroy any Klingons in supernovaed quadrant
2663     kldead = game.state.galaxy[nq.i][nq.j].klingons
2664     game.state.galaxy[nq.i][nq.j].klingons = 0
2665     if nq == game.state.kscmdr:
2666         # did in the Supercommander!
2667         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2668         game.iscate = False
2669         unschedule(FSCMOVE)
2670         unschedule(FSCDBAS)
2671     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2672     comkills = len(game.state.kcmdr) - len(survivors)
2673     game.state.kcmdr = survivors
2674     kldead -= comkills
2675     if not game.state.kcmdr:
2676         unschedule(FTBEAM)
2677     game.state.remkl -= kldead
2678     # destroy Romulans and planets in supernovaed quadrant
2679     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2680     game.state.galaxy[nq.i][nq.j].romulans = 0
2681     game.state.nromrem -= nrmdead
2682     # Destroy planets
2683     for loop in range(game.inplan):
2684         if game.state.planets[loop].quadrant == nq:
2685             game.state.planets[loop].pclass = "destroyed"
2686             npdead += 1
2687     # Destroy any base in supernovaed quadrant
2688     game.state.baseq = [x for x in game.state.baseq if x != nq]
2689     # If starship caused supernova, tally up destruction
2690     if w != None:
2691         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2692         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2693         game.state.nplankl += npdead
2694     # mark supernova in galaxy and in star chart
2695     if game.quadrant == nq or communicating():
2696         game.state.galaxy[nq.i][nq.j].supernova = True
2697     # If supernova destroys last Klingons give special message
2698     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2699         skip(2)
2700         if w is None:
2701             prout(_("Lucky you!"))
2702         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2703         finish(FWON)
2704         return
2705     # if some Klingons remain, continue or die in supernova
2706     if game.alldone:
2707         finish(FSNOVAED)
2708     return
2709
2710 # Code from finish.c ends here.
2711
2712 def selfdestruct():
2713     "Self-destruct maneuver. Finish with a BANG!"
2714     scanner.chew()
2715     if damaged(DCOMPTR):
2716         prout(_("Computer damaged; cannot execute destruct sequence."))
2717         return
2718     prouts(_("---WORKING---")); skip(1)
2719     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2720     prouts("   10"); skip(1)
2721     prouts("       9"); skip(1)
2722     prouts("          8"); skip(1)
2723     prouts("             7"); skip(1)
2724     prouts("                6"); skip(1)
2725     skip(1)
2726     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2727     skip(1)
2728     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2729     skip(1)
2730     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2731     skip(1)
2732     scanner.nexttok()
2733     if game.passwd != scanner.token:
2734         prouts(_("PASSWORD-REJECTED;"))
2735         skip(1)
2736         prouts(_("CONTINUITY-EFFECTED"))
2737         skip(2)
2738         return
2739     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2740     prouts("                   5"); skip(1)
2741     prouts("                      4"); skip(1)
2742     prouts("                         3"); skip(1)
2743     prouts("                            2"); skip(1)
2744     prouts("                              1"); skip(1)
2745     if withprob(0.15):
2746         prouts(_("GOODBYE-CRUEL-WORLD"))
2747         skip(1)
2748     kaboom()
2749
2750 def kaboom():
2751     stars()
2752     if game.ship=='E':
2753         prouts("***")
2754     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2755     skip(1)
2756     stars()
2757     skip(1)
2758     if len(game.enemies) != 0:
2759         whammo = 25.0 * game.energy
2760         for l in range(len(game.enemies)):
2761             if game.enemies[l].power*game.enemies[l].kdist <= whammo:
2762                 deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
2763     finish(FDILITHIUM)
2764
2765 def killrate():
2766     "Compute our rate of kils over time."
2767     elapsed = game.state.date - game.indate
2768     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
2769         return 0
2770     else:
2771         starting = (game.inkling + game.incom + game.inscom)
2772         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2773         return (starting - remaining)/elapsed
2774
2775 def badpoints():
2776     "Compute demerits."
2777     badpt = 5.0*game.state.starkl + \
2778             game.casual + \
2779             10.0*game.state.nplankl + \
2780             300*game.state.nworldkl + \
2781             45.0*game.nhelp +\
2782             100.0*game.state.basekl +\
2783             3.0*game.abandoned
2784     if game.ship == 'F':
2785         badpt += 100.0
2786     elif game.ship is None:
2787         badpt += 200.0
2788     return badpt
2789
2790 def finish(ifin):
2791     # end the game, with appropriate notfications
2792     igotit = False
2793     game.alldone = True
2794     skip(3)
2795     prout(_("It is stardate %.1f.") % game.state.date)
2796     skip(1)
2797     if ifin == FWON: # Game has been won
2798         if game.state.nromrem != 0:
2799             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2800                   game.state.nromrem)
2801
2802         prout(_("You have smashed the Klingon invasion fleet and saved"))
2803         prout(_("the Federation."))
2804         game.gamewon = True
2805         if game.alive:
2806             badpt = badpoints()
2807             if badpt < 100.0:
2808                 badpt = 0.0        # Close enough!
2809             # killsPerDate >= RateMax
2810             if game.state.date-game.indate < 5.0 or \
2811                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2812                 skip(1)
2813                 prout(_("In fact, you have done so well that Starfleet Command"))
2814                 if game.skill == SKILL_NOVICE:
2815                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2816                 elif game.skill == SKILL_FAIR:
2817                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2818                 elif game.skill == SKILL_GOOD:
2819                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2820                 elif game.skill == SKILL_EXPERT:
2821                     prout(_("promotes you to Commodore Emeritus."))
2822                     skip(1)
2823                     prout(_("Now that you think you're really good, try playing"))
2824                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2825                 elif game.skill == SKILL_EMERITUS:
2826                     skip(1)
2827                     proutn(_("Computer-  "))
2828                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2829                     skip(2)
2830                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2831                     skip(1)
2832                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2833                     skip(1)
2834                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2835                     skip(1)
2836                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2837                     skip(1)
2838                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2839                     skip(2)
2840                     prout(_("Now you can retire and write your own Star Trek game!"))
2841                     skip(1)
2842                 elif game.skill >= SKILL_EXPERT:
2843                     if game.thawed and not game.idebug:
2844                         prout(_("You cannot get a citation, so..."))
2845                     else:
2846                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
2847                         scanner.chew()
2848                         if ja():
2849                             igotit = True
2850             # Only grant long life if alive (original didn't!)
2851             skip(1)
2852             prout(_("LIVE LONG AND PROSPER."))
2853         score()
2854         if igotit:
2855             plaque()
2856         return
2857     elif ifin == FDEPLETE: # Federation Resources Depleted
2858         prout(_("Your time has run out and the Federation has been"))
2859         prout(_("conquered.  Your starship is now Klingon property,"))
2860         prout(_("and you are put on trial as a war criminal.  On the"))
2861         proutn(_("basis of your record, you are "))
2862         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
2863             prout(_("acquitted."))
2864             skip(1)
2865             prout(_("LIVE LONG AND PROSPER."))
2866         else:
2867             prout(_("found guilty and"))
2868             prout(_("sentenced to death by slow torture."))
2869             game.alive = False
2870         score()
2871         return
2872     elif ifin == FLIFESUP:
2873         prout(_("Your life support reserves have run out, and"))
2874         prout(_("you die of thirst, starvation, and asphyxiation."))
2875         prout(_("Your starship is a derelict in space."))
2876     elif ifin == FNRG:
2877         prout(_("Your energy supply is exhausted."))
2878         skip(1)
2879         prout(_("Your starship is a derelict in space."))
2880     elif ifin == FBATTLE:
2881         prout(_("The %s has been destroyed in battle.") % crmshp())
2882         skip(1)
2883         prout(_("Dulce et decorum est pro patria mori."))
2884     elif ifin == FNEG3:
2885         prout(_("You have made three attempts to cross the negative energy"))
2886         prout(_("barrier which surrounds the galaxy."))
2887         skip(1)
2888         prout(_("Your navigation is abominable."))
2889         score()
2890     elif ifin == FNOVA:
2891         prout(_("Your starship has been destroyed by a nova."))
2892         prout(_("That was a great shot."))
2893         skip(1)
2894     elif ifin == FSNOVAED:
2895         prout(_("The %s has been fried by a supernova.") % crmshp())
2896         prout(_("...Not even cinders remain..."))
2897     elif ifin == FABANDN:
2898         prout(_("You have been captured by the Klingons. If you still"))
2899         prout(_("had a starbase to be returned to, you would have been"))
2900         prout(_("repatriated and given another chance. Since you have"))
2901         prout(_("no starbases, you will be mercilessly tortured to death."))
2902     elif ifin == FDILITHIUM:
2903         prout(_("Your starship is now an expanding cloud of subatomic particles"))
2904     elif ifin == FMATERIALIZE:
2905         prout(_("Starbase was unable to re-materialize your starship."))
2906         prout(_("Sic transit gloria mundi"))
2907     elif ifin == FPHASER:
2908         prout(_("The %s has been cremated by its own phasers.") % crmshp())
2909     elif ifin == FLOST:
2910         prout(_("You and your landing party have been"))
2911         prout(_("converted to energy, disipating through space."))
2912     elif ifin == FMINING:
2913         prout(_("You are left with your landing party on"))
2914         prout(_("a wild jungle planet inhabited by primitive cannibals."))
2915         skip(1)
2916         prout(_("They are very fond of \"Captain Kirk\" soup."))
2917         skip(1)
2918         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2919     elif ifin == FDPLANET:
2920         prout(_("You and your mining party perish."))
2921         skip(1)
2922         prout(_("That was a great shot."))
2923         skip(1)
2924     elif ifin == FSSC:
2925         prout(_("The Galileo is instantly annihilated by the supernova."))
2926         prout(_("You and your mining party are atomized."))
2927         skip(1)
2928         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2929         prout(_("joins the Romulans, wreaking terror on the Federation."))
2930     elif ifin == FPNOVA:
2931         prout(_("You and your mining party are atomized."))
2932         skip(1)
2933         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2934         prout(_("joins the Romulans, wreaking terror on the Federation."))
2935     elif ifin == FSTRACTOR:
2936         prout(_("The shuttle craft Galileo is also caught,"))
2937         prout(_("and breaks up under the strain."))
2938         skip(1)
2939         prout(_("Your debris is scattered for millions of miles."))
2940         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2941     elif ifin == FDRAY:
2942         prout(_("The mutants attack and kill Spock."))
2943         prout(_("Your ship is captured by Klingons, and"))
2944         prout(_("your crew is put on display in a Klingon zoo."))
2945     elif ifin == FTRIBBLE:
2946         prout(_("Tribbles consume all remaining water,"))
2947         prout(_("food, and oxygen on your ship."))
2948         skip(1)
2949         prout(_("You die of thirst, starvation, and asphyxiation."))
2950         prout(_("Your starship is a derelict in space."))
2951     elif ifin == FHOLE:
2952         prout(_("Your ship is drawn to the center of the black hole."))
2953         prout(_("You are crushed into extremely dense matter."))
2954     elif ifin == FCREW:
2955         prout(_("Your last crew member has died."))
2956     if game.ship == 'F':
2957         game.ship = None
2958     elif game.ship == 'E':
2959         game.ship = 'F'
2960     game.alive = False
2961     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
2962         goodies = game.state.remres/game.inresor
2963         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
2964         if goodies/baddies >= randreal(1.0, 1.5):
2965             prout(_("As a result of your actions, a treaty with the Klingon"))
2966             prout(_("Empire has been signed. The terms of the treaty are"))
2967             if goodies/baddies >= randreal(3.0):
2968                 prout(_("favorable to the Federation."))
2969                 skip(1)
2970                 prout(_("Congratulations!"))
2971             else:
2972                 prout(_("highly unfavorable to the Federation."))
2973         else:
2974             prout(_("The Federation will be destroyed."))
2975     else:
2976         prout(_("Since you took the last Klingon with you, you are a"))
2977         prout(_("martyr and a hero. Someday maybe they'll erect a"))
2978         prout(_("statue in your memory. Rest in peace, and try not"))
2979         prout(_("to think about pigeons."))
2980         game.gamewon = True
2981     score()
2982
2983 def score():
2984     "Compute player's score."
2985     timused = game.state.date - game.indate
2986     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
2987         timused = 5.0
2988     game.perdate = killrate()
2989     ithperd = 500*game.perdate + 0.5
2990     iwon = 0
2991     if game.gamewon:
2992         iwon = 100*game.skill
2993     if game.ship == 'E':
2994         klship = 0
2995     elif game.ship == 'F':
2996         klship = 1
2997     else:
2998         klship = 2
2999     game.score = 10*(game.inkling - game.state.remkl) \
3000              + 50*(game.incom - len(game.state.kcmdr)) \
3001              + ithperd + iwon \
3002              + 20*(game.inrom - game.state.nromrem) \
3003              + 200*(game.inscom - game.state.nscrem) \
3004                  - game.state.nromrem \
3005              - badpoints()
3006     if not game.alive:
3007         game.score -= 200
3008     skip(2)
3009     prout(_("Your score --"))
3010     if game.inrom - game.state.nromrem:
3011         prout(_("%6d Romulans destroyed                 %5d") %
3012               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3013     if game.state.nromrem and game.gamewon:
3014         prout(_("%6d Romulans captured                  %5d") %
3015               (game.state.nromrem, game.state.nromrem))
3016     if game.inkling - game.state.remkl:
3017         prout(_("%6d ordinary Klingons destroyed        %5d") %
3018               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
3019     if game.incom - len(game.state.kcmdr):
3020         prout(_("%6d Klingon commanders destroyed       %5d") %
3021               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3022     if game.inscom - game.state.nscrem:
3023         prout(_("%6d Super-Commander destroyed          %5d") %
3024               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3025     if ithperd:
3026         prout(_("%6.2f Klingons per stardate              %5d") %
3027               (game.perdate, ithperd))
3028     if game.state.starkl:
3029         prout(_("%6d stars destroyed by your action     %5d") %
3030               (game.state.starkl, -5*game.state.starkl))
3031     if game.state.nplankl:
3032         prout(_("%6d planets destroyed by your action   %5d") %
3033               (game.state.nplankl, -10*game.state.nplankl))
3034     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3035         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3036               (game.state.nworldkl, -300*game.state.nworldkl))
3037     if game.state.basekl:
3038         prout(_("%6d bases destroyed by your action     %5d") %
3039               (game.state.basekl, -100*game.state.basekl))
3040     if game.nhelp:
3041         prout(_("%6d calls for help from starbase       %5d") %
3042               (game.nhelp, -45*game.nhelp))
3043     if game.casual:
3044         prout(_("%6d casualties incurred                %5d") %
3045               (game.casual, -game.casual))
3046     if game.abandoned:
3047         prout(_("%6d crew abandoned in space            %5d") %
3048               (game.abandoned, -3*game.abandoned))
3049     if klship:
3050         prout(_("%6d ship(s) lost or destroyed          %5d") %
3051               (klship, -100*klship))
3052     if not game.alive:
3053         prout(_("Penalty for getting yourself killed        -200"))
3054     if game.gamewon:
3055         proutn(_("Bonus for winning "))
3056         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3057         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3058         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3059         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3060         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3061         prout("           %5d" % iwon)
3062     skip(1)
3063     prout(_("TOTAL SCORE                               %5d") % game.score)
3064
3065 def plaque():
3066     "Emit winner's commemmorative plaque."
3067     skip(2)
3068     while True:
3069         proutn(_("File or device name for your plaque: "))
3070         winner = cgetline()
3071         try:
3072             fp = open(winner, "w")
3073             break
3074         except IOError:
3075             prout(_("Invalid name."))
3076
3077     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3078     winner = cgetline()
3079     # The 38 below must be 64 for 132-column paper
3080     nskip = 38 - len(winner)/2
3081     fp.write("\n\n\n\n")
3082     # --------DRAW ENTERPRISE PICTURE.
3083     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3084     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3085     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3086     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3087     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3088     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3089     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3090     fp.write("                                      EEE           E          E            E  E\n")
3091     fp.write("                                                       E         E          E  E\n")
3092     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3093     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3094     fp.write("                                                    :E    :                 EEEE       E\n")
3095     fp.write("                                                   .-E   -:-----                       E\n")
3096     fp.write("                                                    :E    :                            E\n")
3097     fp.write("                                                      EE  :                    EEEEEEEE\n")
3098     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3099     fp.write("\n\n\n")
3100     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3101     fp.write("\n\n\n\n")
3102     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3103     fp.write("\n")
3104     fp.write(_("                                                Starfleet Command bestows to you\n"))
3105     fp.write("\n")
3106     fp.write("%*s%s\n\n" % (nskip, "", winner))
3107     fp.write(_("                                                           the rank of\n\n"))
3108     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3109     fp.write("                                                          ")
3110     if game.skill ==  SKILL_EXPERT:
3111         fp.write(_(" Expert level\n\n"))
3112     elif game.skill == SKILL_EMERITUS:
3113         fp.write(_("Emeritus level\n\n"))
3114     else:
3115         fp.write(_(" Cheat level\n\n"))
3116     timestring = time.ctime()
3117     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3118              (timestring+4, timestring+20, timestring+11))
3119     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3120     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3121     fp.close()
3122
3123 # Code from io.c begins here
3124
3125 rows = linecount = 0        # for paging
3126 stdscr = None
3127 replayfp = None
3128 fullscreen_window = None
3129 srscan_window     = None   # Short range scan
3130 report_window     = None   # Report legends for status window
3131 status_window     = None   # The status window itself
3132 lrscan_window     = None   # Long range scan
3133 message_window    = None   # Main window for scrolling text
3134 prompt_window     = None   # Prompt window at bottom of display
3135 curwnd = None
3136
3137 def iostart():
3138     global stdscr, rows
3139     # for some recent versions of python2, the following enables UTF8
3140     # for the older ones we probably need to set C locale, and python3
3141     # has no problems at all
3142     if sys.version_info[0] < 3:
3143         locale.setlocale(locale.LC_ALL, "")
3144     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3145     gettext.textdomain("sst")
3146     if not (game.options & OPTION_CURSES):
3147         ln_env = os.getenv("LINES")
3148         if ln_env:
3149             rows = ln_env
3150         else:
3151             rows = 25
3152     else:
3153         stdscr = curses.initscr()
3154         stdscr.keypad(True)
3155         curses.nonl()
3156         curses.cbreak()
3157         if game.options & OPTION_COLOR:
3158             curses.start_color()
3159             curses.use_default_colors()
3160             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3161             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3162             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3163             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3164             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3165             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3166             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3167             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3168         global fullscreen_window, srscan_window, report_window, status_window
3169         global lrscan_window, message_window, prompt_window
3170         (rows, _columns)   = stdscr.getmaxyx()
3171         fullscreen_window = stdscr
3172         srscan_window     = curses.newwin(12, 25, 0,       0)
3173         report_window     = curses.newwin(11, 0,  1,       25)
3174         status_window     = curses.newwin(10, 0,  1,       39)
3175         lrscan_window     = curses.newwin(5,  0,  0,       64)
3176         message_window    = curses.newwin(0,  0,  12,      0)
3177         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3178         message_window.scrollok(True)
3179         setwnd(fullscreen_window)
3180
3181 def ioend():
3182     "Wrap up I/O."
3183     if game.options & OPTION_CURSES:
3184         stdscr.keypad(False)
3185         curses.echo()
3186         curses.nocbreak()
3187         curses.endwin()
3188
3189 def waitfor():
3190     "Wait for user action -- OK to do nothing if on a TTY"
3191     if game.options & OPTION_CURSES:
3192         stdscr.getch()
3193
3194 def announce():
3195     skip(1)
3196     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3197     skip(1)
3198
3199 def pause_game():
3200     if game.skill > SKILL_FAIR:
3201         prompt = _("[CONTINUE?]")
3202     else:
3203         prompt = _("[PRESS ENTER TO CONTINUE]")
3204
3205     if game.options & OPTION_CURSES:
3206         drawmaps(0)
3207         setwnd(prompt_window)
3208         prompt_window.clear()
3209         prompt_window.addstr(prompt)
3210         prompt_window.getstr()
3211         prompt_window.clear()
3212         prompt_window.refresh()
3213         setwnd(message_window)
3214     else:
3215         global linecount
3216         sys.stdout.write('\n')
3217         proutn(prompt)
3218         if not replayfp:
3219             input()
3220         sys.stdout.write('\n' * rows)
3221         linecount = 0
3222
3223 def skip(i):
3224     "Skip i lines.  Pause game if this would cause a scrolling event."
3225     for _dummy in range(i):
3226         if game.options & OPTION_CURSES:
3227             (y, _x) = curwnd.getyx()
3228             try:
3229                 curwnd.move(y+1, 0)
3230             except curses.error:
3231                 pass
3232         else:
3233             global linecount
3234             linecount += 1
3235             if rows and linecount >= rows:
3236                 pause_game()
3237             else:
3238                 sys.stdout.write('\n')
3239
3240 def proutn(proutntline):
3241     "Utter a line with no following line feed."
3242     if game.options & OPTION_CURSES:
3243         (y, x) = curwnd.getyx()
3244         (my, _mx) = curwnd.getmaxyx()
3245         if curwnd == message_window and y >= my - 2:
3246             pause_game()
3247             clrscr()
3248         # Uncomment this to debug curses problems
3249         if logfp:
3250             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3251         curwnd.addstr(proutntline)
3252         curwnd.refresh()
3253     else:
3254         sys.stdout.write(proutntline)
3255         sys.stdout.flush()
3256
3257 def prout(proutline):
3258     proutn(proutline)
3259     skip(1)
3260
3261 def prouts(proutsline):
3262     "Emit slowly!"
3263     for c in proutsline:
3264         if not replayfp or replayfp.closed:        # Don't slow down replays
3265             time.sleep(0.03)
3266         proutn(c)
3267         if game.options & OPTION_CURSES:
3268             curwnd.refresh()
3269         else:
3270             sys.stdout.flush()
3271     if not replayfp or replayfp.closed:
3272         time.sleep(0.03)
3273
3274 def cgetline():
3275     "Get a line of input."
3276     if game.options & OPTION_CURSES:
3277         linein = curwnd.getstr() + "\n"
3278         curwnd.refresh()
3279     else:
3280         if replayfp and not replayfp.closed:
3281             while True:
3282                 linein = replayfp.readline()
3283                 proutn(linein)
3284                 if linein == '':
3285                     prout("*** Replay finished")
3286                     replayfp.close()
3287                     break
3288                 elif linein[0] != "#":
3289                     break
3290         else:
3291             linein = eval(input()) + "\n"
3292     if logfp:
3293         logfp.write(linein)
3294     return linein
3295
3296 def setwnd(wnd):
3297     "Change windows -- OK for this to be a no-op in tty mode."
3298     global curwnd
3299     if game.options & OPTION_CURSES:
3300         # Uncomment this to debug curses problems
3301         if logfp:
3302             if wnd == fullscreen_window:
3303                 legend = "fullscreen"
3304             elif wnd == srscan_window:
3305                 legend = "srscan"
3306             elif wnd == report_window:
3307                 legend = "report"
3308             elif wnd == status_window:
3309                 legend = "status"
3310             elif wnd == lrscan_window:
3311                 legend = "lrscan"
3312             elif wnd == message_window:
3313                 legend = "message"
3314             elif wnd == prompt_window:
3315                 legend = "prompt"
3316             else:
3317                 legend = "unknown"
3318             logfp.write("#curses: setwnd(%s)\n" % legend)
3319         curwnd = wnd
3320         # Some curses implementations get confused when you try this.
3321         try:
3322             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3323         except curses.error:
3324             pass
3325
3326 def clreol():
3327     "Clear to end of line -- can be a no-op in tty mode"
3328     if game.options & OPTION_CURSES:
3329         curwnd.clrtoeol()
3330         curwnd.refresh()
3331
3332 def clrscr():
3333     "Clear screen -- can be a no-op in tty mode."
3334     global linecount
3335     if game.options & OPTION_CURSES:
3336         curwnd.clear()
3337         curwnd.move(0, 0)
3338         curwnd.refresh()
3339     linecount = 0
3340
3341 def textcolor(color=DEFAULT):
3342     if game.options & OPTION_COLOR:
3343         if color == DEFAULT:
3344             curwnd.attrset(0)
3345         elif color ==  BLACK:
3346             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3347         elif color ==  BLUE:
3348             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3349         elif color ==  GREEN:
3350             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3351         elif color ==  CYAN:
3352             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3353         elif color ==  RED:
3354             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3355         elif color ==  MAGENTA:
3356             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3357         elif color ==  BROWN:
3358             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3359         elif color ==  LIGHTGRAY:
3360             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3361         elif color ==  DARKGRAY:
3362             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3363         elif color ==  LIGHTBLUE:
3364             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3365         elif color ==  LIGHTGREEN:
3366             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3367         elif color ==  LIGHTCYAN:
3368             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3369         elif color ==  LIGHTRED:
3370             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3371         elif color ==  LIGHTMAGENTA:
3372             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3373         elif color ==  YELLOW:
3374             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3375         elif color ==  WHITE:
3376             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3377
3378 def highvideo():
3379     if game.options & OPTION_COLOR:
3380         curwnd.attron(curses.A_REVERSE)
3381
3382 #
3383 # Things past this point have policy implications.
3384 #
3385
3386 def drawmaps(mode):
3387     "Hook to be called after moving to redraw maps."
3388     if game.options & OPTION_CURSES:
3389         if mode == 1:
3390             sensor()
3391         setwnd(srscan_window)
3392         curwnd.move(0, 0)
3393         srscan()
3394         if mode != 2:
3395             setwnd(status_window)
3396             status_window.clear()
3397             status_window.move(0, 0)
3398             setwnd(report_window)
3399             report_window.clear()
3400             report_window.move(0, 0)
3401             status()
3402             setwnd(lrscan_window)
3403             lrscan_window.clear()
3404             lrscan_window.move(0, 0)
3405             lrscan(silent=False)
3406
3407 def put_srscan_sym(w, sym):
3408     "Emit symbol for short-range scan."
3409     srscan_window.move(w.i+1, w.j*2+2)
3410     srscan_window.addch(sym)
3411     srscan_window.refresh()
3412
3413 def boom(w):
3414     "Enemy fall down, go boom."
3415     if game.options & OPTION_CURSES:
3416         drawmaps(2)
3417         setwnd(srscan_window)
3418         srscan_window.attron(curses.A_REVERSE)
3419         put_srscan_sym(w, game.quad[w.i][w.j])
3420         #sound(500)
3421         #time.sleep(1.0)
3422         #nosound()
3423         srscan_window.attroff(curses.A_REVERSE)
3424         put_srscan_sym(w, game.quad[w.i][w.j])
3425         curses.delay_output(500)
3426         setwnd(message_window)
3427
3428 def warble():
3429     "Sound and visual effects for teleportation."
3430     if game.options & OPTION_CURSES:
3431         drawmaps(2)
3432         setwnd(message_window)
3433         #sound(50)
3434     prouts("     . . . . .     ")
3435     if game.options & OPTION_CURSES:
3436         #curses.delay_output(1000)
3437         #nosound()
3438         pass
3439
3440 def tracktorpedo(w, step, i, n, iquad):
3441     "Torpedo-track animation."
3442     if not game.options & OPTION_CURSES:
3443         if step == 1:
3444             if n != 1:
3445                 skip(1)
3446                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3447             else:
3448                 skip(1)
3449                 proutn(_("Torpedo track- "))
3450         elif step==4 or step==9:
3451             skip(1)
3452         proutn("%s   " % w)
3453     else:
3454         if not damaged(DSRSENS) or game.condition=="docked":
3455             if i != 0 and step == 1:
3456                 drawmaps(2)
3457                 time.sleep(0.4)
3458             if (iquad=='.') or (iquad==' '):
3459                 put_srscan_sym(w, '+')
3460                 #sound(step*10)
3461                 #time.sleep(0.1)
3462                 #nosound()
3463                 put_srscan_sym(w, iquad)
3464             else:
3465                 curwnd.attron(curses.A_REVERSE)
3466                 put_srscan_sym(w, iquad)
3467                 #sound(500)
3468                 #time.sleep(1.0)
3469                 #nosound()
3470                 curwnd.attroff(curses.A_REVERSE)
3471                 put_srscan_sym(w, iquad)
3472         else:
3473             proutn("%s   " % w)
3474
3475 def makechart():
3476     "Display the current galaxy chart."
3477     if game.options & OPTION_CURSES:
3478         setwnd(message_window)
3479         message_window.clear()
3480     chart()
3481     if game.options & OPTION_TTY:
3482         skip(1)
3483
3484 NSYM        = 14
3485
3486 def prstat(txt, data):
3487     proutn(txt)
3488     if game.options & OPTION_CURSES:
3489         skip(1)
3490         setwnd(status_window)
3491     else:
3492         proutn(" " * (NSYM - len(txt)))
3493     proutn(data)
3494     skip(1)
3495     if game.options & OPTION_CURSES:
3496         setwnd(report_window)
3497
3498 # Code from moving.c begins here
3499
3500 def imove(icourse=None, noattack=False):
3501     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3502     w = Coord()
3503
3504     def newquadrant(noattack):
3505         # Leaving quadrant -- allow final enemy attack
3506         # Don't do it if being pushed by Nova
3507         if len(game.enemies) != 0 and not noattack:
3508             newcnd()
3509             for enemy in game.enemies:
3510                 finald = (w - enemy.location).distance()
3511                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3512             # Stas Sergeev added the condition
3513             # that attacks only happen if Klingons
3514             # are present and your skill is good.
3515             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3516                 attack(torps_ok=False)
3517             if game.alldone:
3518                 return
3519         # check for edge of galaxy
3520         kinks = 0
3521         while True:
3522
3523
3524             kink = False
3525             if icourse.final.i < 0:
3526                 icourse.final.i = -icourse.final.i
3527                 kink = True
3528             if icourse.final.j < 0:
3529                 icourse.final.j = -icourse.final.j
3530                 kink = True
3531             if icourse.final.i >= GALSIZE*QUADSIZE:
3532                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3533                 kink = True
3534             if icourse.final.j >= GALSIZE*QUADSIZE:
3535                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3536                 kink = True
3537             if kink:
3538                 kinks += 1
3539             else:
3540                 break
3541         if kinks:
3542             game.nkinks += 1
3543             if game.nkinks == 3:
3544                 # Three strikes -- you're out!
3545                 finish(FNEG3)
3546                 return
3547             skip(1)
3548             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3549             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3550             prout(_("YOU WILL BE DESTROYED."))
3551         # Compute final position in new quadrant
3552         if trbeam: # Don't bother if we are to be beamed
3553             return
3554         game.quadrant = icourse.final.quadrant()
3555         game.sector = icourse.final.sector()
3556         skip(1)
3557         prout(_("Entering Quadrant %s.") % game.quadrant)
3558         game.quad[game.sector.i][game.sector.j] = game.ship
3559         newqad()
3560         if game.skill>SKILL_NOVICE:
3561             attack(torps_ok=False)
3562
3563     def check_collision(h):
3564         iquad = game.quad[h.i][h.j]
3565         if iquad != '.':
3566             # object encountered in flight path
3567             stopegy = 50.0*icourse.distance/game.optime
3568             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3569                 for enemy in game.enemies:
3570                     if enemy.location == game.sector:
3571                         collision(rammed=False, enemy=enemy)
3572                         return True
3573                 # This should not happen
3574                 prout(_("Which way did he go?"))
3575                 return False
3576             elif iquad == ' ':
3577                 skip(1)
3578                 prouts(_("***RED ALERT!  RED ALERT!"))
3579                 skip(1)
3580                 proutn("***" + crmshp())
3581                 proutn(_(" pulled into black hole at Sector %s") % h)
3582                 # Getting pulled into a black hole was certain
3583                 # death in Almy's original.  Stas Sergeev added a
3584                 # possibility that you'll get timewarped instead.
3585                 n=0
3586                 for m in range(NDEVICES):
3587                     if game.damage[m]>0:
3588                         n += 1
3589                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3590                 if (game.options & OPTION_BLKHOLE) and withprob(1-probf):
3591                     timwrp()
3592                 else:
3593                     finish(FHOLE)
3594                 return True
3595             else:
3596                 # something else
3597                 skip(1)
3598                 proutn(crmshp())
3599                 if iquad == '#':
3600                     prout(_(" encounters Tholian web at %s;") % h)
3601                 else:
3602                     prout(_(" blocked by object at %s;") % h)
3603                 proutn(_("Emergency stop required "))
3604                 prout(_("%2d units of energy.") % int(stopegy))
3605                 game.energy -= stopegy
3606                 if game.energy <= 0:
3607                     finish(FNRG)
3608                 return True
3609         return False
3610
3611     trbeam = False
3612     if game.inorbit:
3613         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3614         game.inorbit = False
3615     # If tractor beam is to occur, don't move full distance
3616     if game.state.date+game.optime >= scheduled(FTBEAM):
3617         trbeam = True
3618         game.condition = "red"
3619         icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3620         game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3621     # Move out
3622     game.quad[game.sector.i][game.sector.j] = '.'
3623     for _m in range(icourse.moves):
3624         icourse.nexttok()
3625         w = icourse.sector()
3626         if icourse.origin.quadrant() != icourse.location.quadrant():
3627             newquadrant(noattack)
3628             break
3629         elif check_collision(w):
3630             print("Collision detected")
3631             break
3632         else:
3633             game.sector = w
3634     # We're in destination quadrant -- compute new average enemy distances
3635     game.quad[game.sector.i][game.sector.j] = game.ship
3636     if game.enemies:
3637         for enemy in game.enemies:
3638             finald = (w-enemy.location).distance()
3639             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3640             enemy.kdist = finald
3641         sortenemies()
3642         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3643             attack(torps_ok=False)
3644         for enemy in game.enemies:
3645             enemy.kavgd = enemy.kdist
3646     newcnd()
3647     drawmaps(0)
3648     setwnd(message_window)
3649     return
3650
3651 def dock(verbose):
3652     "Dock our ship at a starbase."
3653     scanner.chew()
3654     if game.condition == "docked" and verbose:
3655         prout(_("Already docked."))
3656         return
3657     if game.inorbit:
3658         prout(_("You must first leave standard orbit."))
3659         return
3660     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3661         prout(crmshp() + _(" not adjacent to base."))
3662         return
3663     game.condition = "docked"
3664     if verbose:
3665         prout(_("Docked."))
3666     game.ididit = True
3667     if game.energy < game.inenrg:
3668         game.energy = game.inenrg
3669     game.shield = game.inshld
3670     game.torps = game.intorps
3671     game.lsupres = game.inlsr
3672     game.state.crew = FULLCREW
3673     if not damaged(DRADIO) and \
3674         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3675         # get attack report from base
3676         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3677         attackreport(False)
3678         game.iseenit = True
3679
3680 def cartesian(loc1=None, loc2=None):
3681     if loc1 is None:
3682         return game.quadrant * QUADSIZE + game.sector
3683     elif loc2 is None:
3684         return game.quadrant * QUADSIZE + loc1
3685     else:
3686         return loc1 * QUADSIZE + loc2
3687
3688 def getcourse(isprobe):
3689     "Get a course and distance from the user."
3690     key = ""
3691     dquad = copy.copy(game.quadrant)
3692     navmode = "unspecified"
3693     itemp = "curt"
3694     dsect = Coord()
3695     iprompt = False
3696     if game.landed and not isprobe:
3697         prout(_("Dummy! You can't leave standard orbit until you"))
3698         proutn(_("are back aboard the ship."))
3699         scanner.chew()
3700         raise TrekError
3701     while navmode == "unspecified":
3702         if damaged(DNAVSYS):
3703             if isprobe:
3704                 prout(_("Computer damaged; manual navigation only"))
3705             else:
3706                 prout(_("Computer damaged; manual movement only"))
3707             scanner.chew()
3708             navmode = "manual"
3709             key = "IHEOL"
3710             break
3711         key = scanner.nexttok()
3712         if key == "IHEOL":
3713             proutn(_("Manual or automatic- "))
3714             iprompt = True
3715             scanner.chew()
3716         elif key == "IHALPHA":
3717             if scanner.sees("manual"):
3718                 navmode = "manual"
3719                 key = scanner.nexttok()
3720                 break
3721             elif scanner.sees("automatic"):
3722                 navmode = "automatic"
3723                 key = scanner.nexttok()
3724                 break
3725             else:
3726                 huh()
3727                 scanner.chew()
3728                 raise TrekError
3729         else: # numeric
3730             if isprobe:
3731                 prout(_("(Manual navigation assumed.)"))
3732             else:
3733                 prout(_("(Manual movement assumed.)"))
3734             navmode = "manual"
3735             break
3736     delta = Coord()
3737     if navmode == "automatic":
3738         while key == "IHEOL":
3739             if isprobe:
3740                 proutn(_("Target quadrant or quadrant&sector- "))
3741             else:
3742                 proutn(_("Destination sector or quadrant&sector- "))
3743             scanner.chew()
3744             iprompt = True
3745             key = scanner.nexttok()
3746         if key != "IHREAL":
3747             huh()
3748             raise TrekError
3749         xi = int(round(scanner.real))-1
3750         key = scanner.nexttok()
3751         if key != "IHREAL":
3752             huh()
3753             raise TrekError
3754         xj = int(round(scanner.real))-1
3755         key = scanner.nexttok()
3756         if key == "IHREAL":
3757             # both quadrant and sector specified
3758             xk = int(round(scanner.real))-1
3759             key = scanner.nexttok()
3760             if key != "IHREAL":
3761                 huh()
3762                 raise TrekError
3763             xl = int(round(scanner.real))-1
3764             dquad.i = xi
3765             dquad.j = xj
3766             dsect.i = xk
3767             dsect.j = xl
3768         else:
3769             # only one pair of numbers was specified
3770             if isprobe:
3771                 # only quadrant specified -- go to center of dest quad
3772                 dquad.i = xi
3773                 dquad.j = xj
3774                 dsect.j = dsect.i = 4        # preserves 1-origin behavior
3775             else:
3776                 # only sector specified
3777                 dsect.i = xi
3778                 dsect.j = xj
3779             itemp = "normal"
3780         if not dquad.valid_quadrant() or not dsect.valid_sector():
3781             huh()
3782             raise TrekError
3783         skip(1)
3784         if not isprobe:
3785             if itemp > "curt":
3786                 if iprompt:
3787                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
3788             else:
3789                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
3790         # the actual deltas get computed here
3791         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
3792         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
3793     else: # manual
3794         while key == "IHEOL":
3795             proutn(_("X and Y displacements- "))
3796             scanner.chew()
3797             iprompt = True
3798             key = scanner.nexttok()
3799         itemp = "verbose"
3800         if key != "IHREAL":
3801             huh()
3802             raise TrekError
3803         delta.j = scanner.real
3804         key = scanner.nexttok()
3805         if key != "IHREAL":
3806             huh()
3807             raise TrekError
3808         delta.i = scanner.real
3809     # Check for zero movement
3810     if delta.i == 0 and delta.j == 0:
3811         scanner.chew()
3812         raise TrekError
3813     if itemp == "verbose" and not isprobe:
3814         skip(1)
3815         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
3816     scanner.chew()
3817     return course(bearing=delta.bearing(), distance=delta.distance())
3818
3819 class course:
3820     def __init__(self, bearing, distance, origin=None):
3821         self.distance = distance
3822         self.bearing = bearing
3823         if origin is None:
3824             self.origin = cartesian(game.quadrant, game.sector)
3825         else:
3826             self.origin = origin
3827         # The bearing() code we inherited from FORTRAN is actually computing
3828         # clockface directions!
3829         if self.bearing < 0.0:
3830             self.bearing += 12.0
3831         self.angle = ((15.0 - self.bearing) * 0.5235988)
3832         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
3833         bigger = max(abs(self.increment.i), abs(self.increment.j))
3834         self.increment /= bigger
3835         self.moves = int(round(10*self.distance*bigger))
3836         self.reset()
3837         self.final = (self.location + self.moves*self.increment).roundtogrid()
3838         self.location = self.origin
3839         self.nextlocation = None
3840     def reset(self):
3841         self.location = self.origin
3842         self.step = 0
3843     def arrived(self):
3844         return self.location.roundtogrid() == self.final
3845     def nexttok(self):
3846         "Next step on course."
3847         self.step += 1
3848         self.nextlocation = self.location + self.increment
3849         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
3850         self.location = self.nextlocation
3851         return samequad
3852     def quadrant(self):
3853         return self.location.quadrant()
3854     def sector(self):
3855         return self.location.sector()
3856     def power(self, w):
3857         return self.distance*(w**3)*(game.shldup+1)
3858     def time(self, w):
3859         return 10.0*self.distance/w**2
3860
3861 def impulse():
3862     "Move under impulse power."
3863     game.ididit = False
3864     if damaged(DIMPULS):
3865         scanner.chew()
3866         skip(1)
3867         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
3868         return
3869     if game.energy > 30.0:
3870         try:
3871             icourse = getcourse(isprobe=False)
3872         except TrekError:
3873             return
3874         power = 20.0 + 100.0*icourse.distance
3875     else:
3876         power = 30.0
3877     if power >= game.energy:
3878         # Insufficient power for trip
3879         skip(1)
3880         prout(_("First Officer Spock- \"Captain, the impulse engines"))
3881         prout(_("require 20.0 units to engage, plus 100.0 units per"))
3882         if game.energy > 30:
3883             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
3884                    int(0.01 * (game.energy-20.0)-0.05))
3885             prout(_(" quadrants.\""))
3886         else:
3887             prout(_("quadrant.  They are, therefore, useless.\""))
3888         scanner.chew()
3889         return
3890     # Make sure enough time is left for the trip
3891     game.optime = icourse.distance/0.095
3892     if game.optime >= game.state.remtime:
3893         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
3894         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
3895         proutn(_("we dare spend the time?\" "))
3896         if not ja():
3897             return
3898     # Activate impulse engines and pay the cost
3899     imove(icourse, noattack=False)
3900     game.ididit = True
3901     if game.alldone:
3902         return
3903     power = 20.0 + 100.0*icourse.distance
3904     game.energy -= power
3905     game.optime = icourse.distance/0.095
3906     if game.energy <= 0:
3907         finish(FNRG)
3908     return
3909
3910 def warp(wcourse, involuntary):
3911     "ove under warp drive."
3912     blooey = False; twarp = False
3913     if not involuntary: # Not WARPX entry
3914         game.ididit = False
3915         if game.damage[DWARPEN] > 10.0:
3916             scanner.chew()
3917             skip(1)
3918             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
3919             return
3920         if damaged(DWARPEN) and game.warpfac > 4.0:
3921             scanner.chew()
3922             skip(1)
3923             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
3924             prout(_("  is repaired, I can only give you warp 4.\""))
3925             return
3926                # Read in course and distance
3927         if wcourse is None:
3928             try:
3929                 wcourse = getcourse(isprobe=False)
3930             except TrekError:
3931                 return
3932         # Make sure starship has enough energy for the trip
3933         # Note: this formula is slightly different from the C version,
3934         # and lets you skate a bit closer to the edge.
3935         if wcourse.power(game.warpfac) >= game.energy:
3936             # Insufficient power for trip
3937             game.ididit = False
3938             skip(1)
3939             prout(_("Engineering to bridge--"))
3940             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
3941                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
3942                 if iwarp <= 0:
3943                     prout(_("We can't do it, Captain. We don't have enough energy."))
3944                 else:
3945                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
3946                     if game.shldup:
3947                         prout(",")
3948                         prout(_("if you'll lower the shields."))
3949                     else:
3950                         prout(".")
3951             else:
3952                 prout(_("We haven't the energy to go that far with the shields up."))
3953             return
3954         # Make sure enough time is left for the trip
3955         game.optime = wcourse.time(game.warpfac)
3956         if game.optime >= 0.8*game.state.remtime:
3957             skip(1)
3958             prout(_("First Officer Spock- \"Captain, I compute that such"))
3959             proutn(_("  a trip would require approximately %2.0f") %
3960                    (100.0*game.optime/game.state.remtime))
3961             prout(_(" percent of our"))
3962             proutn(_("  remaining time.  Are you sure this is wise?\" "))
3963             if not ja():
3964                 game.ididit = False
3965                 game.optime=0
3966                 return
3967     # Entry WARPX
3968     if game.warpfac > 6.0:
3969         # Decide if engine damage will occur
3970         # ESR: Seems wrong. Probability of damage goes *down* with distance?
3971         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
3972         if prob > randreal():
3973             blooey = True
3974             wcourse.distance = randreal(wcourse.distance)
3975         # Decide if time warp will occur
3976         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
3977             twarp = True
3978         if game.idebug and game.warpfac==10 and not twarp:
3979             blooey = False
3980             proutn("=== Force time warp? ")
3981             if ja():
3982                 twarp = True
3983         if blooey or twarp:
3984             # If time warp or engine damage, check path
3985             # If it is obstructed, don't do warp or damage
3986             look = wcourse.moves
3987             while look > 0:
3988                 look -= 1
3989                 wcourse.nexttok()
3990                 w = wcourse.sector()
3991                 if not w.valid_sector():
3992                     break
3993                 if game.quad[w.i][w.j] != '.':
3994                     blooey = False
3995                     twarp = False
3996             wcourse.reset()
3997     # Activate Warp Engines and pay the cost
3998     imove(wcourse, noattack=False)
3999     if game.alldone:
4000         return
4001     game.energy -= wcourse.power(game.warpfac)
4002     if game.energy <= 0:
4003         finish(FNRG)
4004     game.optime = wcourse.time(game.warpfac)
4005     if twarp:
4006         timwrp()
4007     if blooey:
4008         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
4009         skip(1)
4010         prout(_("Engineering to bridge--"))
4011         prout(_("  Scott here.  The warp engines are damaged."))
4012         prout(_("  We'll have to reduce speed to warp 4."))
4013     game.ididit = True
4014     return
4015
4016 def setwarp():
4017     "Change the warp factor."
4018     while True:
4019         key=scanner.nexttok()
4020         if key != "IHEOL":
4021             break
4022         scanner.chew()
4023         proutn(_("Warp factor- "))
4024     if key != "IHREAL":
4025         huh()
4026         return
4027     if game.damage[DWARPEN] > 10.0:
4028         prout(_("Warp engines inoperative."))
4029         return
4030     if damaged(DWARPEN) and scanner.real > 4.0:
4031         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4032         prout(_("  but right now we can only go warp 4.\""))
4033         return
4034     if scanner.real > 10.0:
4035         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4036         return
4037     if scanner.real < 1.0:
4038         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4039         return
4040     oldfac = game.warpfac
4041     game.warpfac = scanner.real
4042     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4043         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4044               int(game.warpfac))
4045         return
4046     if game.warpfac < 8.00:
4047         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4048         return
4049     if game.warpfac == 10.0:
4050         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4051         return
4052     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4053     return
4054
4055 def atover(igrab):
4056     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4057     scanner.chew()
4058     # is captain on planet?
4059     if game.landed:
4060         if damaged(DTRANSP):
4061             finish(FPNOVA)
4062             return
4063         prout(_("Scotty rushes to the transporter controls."))
4064         if game.shldup:
4065             prout(_("But with the shields up it's hopeless."))
4066             finish(FPNOVA)
4067         prouts(_("His desperate attempt to rescue you . . ."))
4068         if withprob(0.5):
4069             prout(_("fails."))
4070             finish(FPNOVA)
4071             return
4072         prout(_("SUCCEEDS!"))
4073         if game.imine:
4074             game.imine = False
4075             proutn(_("The crystals mined were "))
4076             if withprob(0.25):
4077                 prout(_("lost."))
4078             else:
4079                 prout(_("saved."))
4080                 game.icrystl = True
4081     if igrab:
4082         return
4083     # Check to see if captain in shuttle craft
4084     if game.icraft:
4085         finish(FSTRACTOR)
4086     if game.alldone:
4087         return
4088     # Inform captain of attempt to reach safety
4089     skip(1)
4090     while True:
4091         if game.justin:
4092             prouts(_("***RED ALERT!  RED ALERT!"))
4093             skip(1)
4094             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4095             prouts(_("   a supernova."))
4096             skip(2)
4097         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4098         prout(_("safely out of quadrant."))
4099         if not damaged(DRADIO):
4100             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4101         # Try to use warp engines
4102         if damaged(DWARPEN):
4103             skip(1)
4104             prout(_("Warp engines damaged."))
4105             finish(FSNOVAED)
4106             return
4107         game.warpfac = randreal(6.0, 8.0)
4108         prout(_("Warp factor set to %d") % int(game.warpfac))
4109         power = 0.75*game.energy
4110         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4111         dist = max(dist, randreal(math.sqrt(2)))
4112         bugout = course(bearing=randreal(12), distance=dist)        # How dumb!
4113         game.optime = bugout.time(game.warpfac)
4114         game.justin = False
4115         game.inorbit = False
4116         warp(bugout, involuntary=True)
4117         if not game.justin:
4118             # This is bad news, we didn't leave quadrant.
4119             if game.alldone:
4120                 return
4121             skip(1)
4122             prout(_("Insufficient energy to leave quadrant."))
4123             finish(FSNOVAED)
4124             return
4125         # Repeat if another snova
4126         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4127             break
4128     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
4129         finish(FWON) # Snova killed remaining enemy.
4130
4131 def timwrp():
4132     "Let's do the time warp again."
4133     prout(_("***TIME WARP ENTERED."))
4134     if game.state.snap and withprob(0.5):
4135         # Go back in time
4136         prout(_("You are traveling backwards in time %d stardates.") %
4137               int(game.state.date-game.snapsht.date))
4138         game.state = game.snapsht
4139         game.state.snap = False
4140         if len(game.state.kcmdr):
4141             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4142             schedule(FBATTAK, expran(0.3*game.intime))
4143         schedule(FSNOVA, expran(0.5*game.intime))
4144         # next snapshot will be sooner
4145         schedule(FSNAP, expran(0.25*game.state.remtime))
4146
4147         if game.state.nscrem:
4148             schedule(FSCMOVE, 0.2777)
4149         game.isatb = 0
4150         unschedule(FCDBAS)
4151         unschedule(FSCDBAS)
4152         game.battle.invalidate()
4153         # Make sure Galileo is consistant -- Snapshot may have been taken
4154         # when on planet, which would give us two Galileos!
4155         gotit = False
4156         for l in range(game.inplan):
4157             if game.state.planets[l].known == "shuttle_down":
4158                 gotit = True
4159                 if game.iscraft == "onship" and game.ship=='E':
4160                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4161                     game.iscraft = "offship"
4162         # Likewise, if in the original time the Galileo was abandoned, but
4163         # was on ship earlier, it would have vanished -- let's restore it.
4164         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4165             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4166             game.iscraft = "onship"
4167         # There used to be code to do the actual reconstrction here,
4168         # but the starchart is now part of the snapshotted galaxy state.
4169         prout(_("Spock has reconstructed a correct star chart from memory"))
4170     else:
4171         # Go forward in time
4172         game.optime = expran(0.5*game.intime)
4173         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4174         # cheat to make sure no tractor beams occur during time warp
4175         postpone(FTBEAM, game.optime)
4176         game.damage[DRADIO] += game.optime
4177     newqad()
4178     events()        # Stas Sergeev added this -- do pending events
4179
4180 def probe():
4181     "Launch deep-space probe."
4182     # New code to launch a deep space probe
4183     if game.nprobes == 0:
4184         scanner.chew()
4185         skip(1)
4186         if game.ship == 'E':
4187             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4188         else:
4189             prout(_("Ye Faerie Queene has no deep space probes."))
4190         return
4191     if damaged(DDSP):
4192         scanner.chew()
4193         skip(1)
4194         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4195         return
4196     if is_scheduled(FDSPROB):
4197         scanner.chew()
4198         skip(1)
4199         if damaged(DRADIO) and game.condition != "docked":
4200             prout(_("Spock-  \"Records show the previous probe has not yet"))
4201             prout(_("   reached its destination.\""))
4202         else:
4203             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4204         return
4205     key = scanner.nexttok()
4206     if key == "IHEOL":
4207         if game.nprobes == 1:
4208             prout(_("1 probe left."))
4209         else:
4210             prout(_("%d probes left") % game.nprobes)
4211         proutn(_("Are you sure you want to fire a probe? "))
4212         if not ja():
4213             return
4214     game.isarmed = False
4215     if key == "IHALPHA" and scanner.token == "armed":
4216         game.isarmed = True
4217         key = scanner.nexttok()
4218     elif key == "IHEOL":
4219         proutn(_("Arm NOVAMAX warhead? "))
4220         game.isarmed = ja()
4221     elif key == "IHREAL":                # first element of course
4222         scanner.push(scanner.token)
4223     try:
4224         game.probe = getcourse(isprobe=True)
4225     except TrekError:
4226         return
4227     game.nprobes -= 1
4228     schedule(FDSPROB, 0.01) # Time to move one sector
4229     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4230     game.ididit = True
4231     return
4232
4233 def mayday():
4234     "Yell for help from nearest starbase."
4235     # There's more than one way to move in this game!
4236     scanner.chew()
4237     # Test for conditions which prevent calling for help
4238     if game.condition == "docked":
4239         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4240         return
4241     if damaged(DRADIO):
4242         prout(_("Subspace radio damaged."))
4243         return
4244     if not game.state.baseq:
4245         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4246         return
4247     if game.landed:
4248         prout(_("You must be aboard the %s.") % crmshp())
4249         return
4250     # OK -- call for help from nearest starbase
4251     game.nhelp += 1
4252     if game.base.i!=0:
4253         # There's one in this quadrant
4254         ddist = (game.base - game.sector).distance()
4255     else:
4256         ibq = None      # Force base-quadrant game to persist past loop
4257         ddist = FOREVER
4258         for ibq in game.state.baseq:
4259             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4260             if xdist < ddist:
4261                 ddist = xdist
4262         if ibq is None:
4263             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4264             return
4265         # Since starbase not in quadrant, set up new quadrant
4266         game.quadrant = ibq
4267         newqad()
4268     # dematerialize starship
4269     game.quad[game.sector.i][game.sector.j]='.'
4270     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4271            % (game.quadrant, crmshp()))
4272     game.sector.invalidate()
4273     for m in range(1, 5+1):
4274         w = game.base.scatter()
4275         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4276             # found one -- finish up
4277             game.sector = w
4278             break
4279     if not game.sector.is_valid():
4280         prout(_("You have been lost in space..."))
4281         finish(FMATERIALIZE)
4282         return
4283     # Give starbase three chances to rematerialize starship
4284     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4285     for m in range(1, 3+1):
4286         if m == 1: proutn(_("1st"))
4287         elif m == 2: proutn(_("2nd"))
4288         elif m == 3: proutn(_("3rd"))
4289         proutn(_(" attempt to re-materialize ") + crmshp())
4290         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4291         textcolor(RED)
4292         warble()
4293         if randreal() > probf:
4294             break
4295         prout(_("fails."))
4296         textcolor(DEFAULT)
4297         curses.delay_output(500)
4298     if m > 3:
4299         game.quad[game.sector.i][game.sector.j]='?'
4300         game.alive = False
4301         drawmaps(1)
4302         setwnd(message_window)
4303         finish(FMATERIALIZE)
4304         return
4305     game.quad[game.sector.i][game.sector.j]=game.ship
4306     textcolor(GREEN)
4307     prout(_("succeeds."))
4308     textcolor(DEFAULT)
4309     dock(False)
4310     skip(1)
4311     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4312
4313 def abandon():
4314     "Abandon ship."
4315     scanner.chew()
4316     if game.condition=="docked":
4317         if game.ship!='E':
4318             prout(_("You cannot abandon Ye Faerie Queene."))
4319             return
4320     else:
4321         # Must take shuttle craft to exit
4322         if game.damage[DSHUTTL]==-1:
4323             prout(_("Ye Faerie Queene has no shuttle craft."))
4324             return
4325         if game.damage[DSHUTTL]<0:
4326             prout(_("Shuttle craft now serving Big Macs."))
4327             return
4328         if game.damage[DSHUTTL]>0:
4329             prout(_("Shuttle craft damaged."))
4330             return
4331         if game.landed:
4332             prout(_("You must be aboard the ship."))
4333             return
4334         if game.iscraft != "onship":
4335             prout(_("Shuttle craft not currently available."))
4336             return
4337         # Emit abandon ship messages
4338         skip(1)
4339         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4340         skip(1)
4341         prouts(_("***ALL HANDS ABANDON SHIP!"))
4342         skip(2)
4343         prout(_("Captain and crew escape in shuttle craft."))
4344         if not game.state.baseq:
4345             # Oops! no place to go...
4346             finish(FABANDN)
4347             return
4348         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4349         # Dispose of crew
4350         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4351             prout(_("Remainder of ship's complement beam down"))
4352             prout(_("to nearest habitable planet."))
4353         elif q.planet != None and not damaged(DTRANSP):
4354             prout(_("Remainder of ship's complement beam down to %s.") %
4355                   q.planet)
4356         else:
4357             prout(_("Entire crew of %d left to die in outer space.") %
4358                   game.state.crew)
4359             game.casual += game.state.crew
4360             game.abandoned += game.state.crew
4361         # If at least one base left, give 'em the Faerie Queene
4362         skip(1)
4363         game.icrystl = False # crystals are lost
4364         game.nprobes = 0 # No probes
4365         prout(_("You are captured by Klingons and released to"))
4366         prout(_("the Federation in a prisoner-of-war exchange."))
4367         nb = randrange(len(game.state.baseq))
4368         # Set up quadrant and position FQ adjacient to base
4369         if not game.quadrant == game.state.baseq[nb]:
4370             game.quadrant = game.state.baseq[nb]
4371             game.sector.i = game.sector.j = 5
4372             newqad()
4373         while True:
4374             # position next to base by trial and error
4375             game.quad[game.sector.i][game.sector.j] = '.'
4376             l = QUADSIZE
4377             for l in range(QUADSIZE):
4378                 game.sector = game.base.scatter()
4379                 if game.sector.valid_sector() and \
4380                        game.quad[game.sector.i][game.sector.j] == '.':
4381                     break
4382             if l < QUADSIZE:
4383                 break # found a spot
4384             game.sector.i=QUADSIZE/2
4385             game.sector.j=QUADSIZE/2
4386             newqad()
4387     # Get new commission
4388     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4389     game.state.crew = FULLCREW
4390     prout(_("Starfleet puts you in command of another ship,"))
4391     prout(_("the Faerie Queene, which is antiquated but,"))
4392     prout(_("still useable."))
4393     if game.icrystl:
4394         prout(_("The dilithium crystals have been moved."))
4395     game.imine = False
4396     game.iscraft = "offship" # Galileo disappears
4397     # Resupply ship
4398     game.condition="docked"
4399     for l in range(NDEVICES):
4400         game.damage[l] = 0.0
4401     game.damage[DSHUTTL] = -1
4402     game.energy = game.inenrg = 3000.0
4403     game.shield = game.inshld = 1250.0
4404     game.torps = game.intorps = 6
4405     game.lsupres=game.inlsr=3.0
4406     game.shldup=False
4407     game.warpfac=5.0
4408     return
4409
4410 # Code from planets.c begins here.
4411
4412 def consumeTime():
4413     "Abort a lengthy operation if an event interrupts it."
4414     game.ididit = True
4415     events()
4416     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4417         return True
4418     return False
4419
4420 def survey():
4421     "Report on (uninhabited) planets in the galaxy."
4422     iknow = False
4423     skip(1)
4424     scanner.chew()
4425     prout(_("Spock-  \"Planet report follows, Captain.\""))
4426     skip(1)
4427     for i in range(game.inplan):
4428         if game.state.planets[i].pclass == "destroyed":
4429             continue
4430         if (game.state.planets[i].known != "unknown" \
4431             and not game.state.planets[i].inhabited) \
4432             or game.idebug:
4433             iknow = True
4434             if game.idebug and game.state.planets[i].known=="unknown":
4435                 proutn("(Unknown) ")
4436             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4437             proutn(_("   class "))
4438             proutn(game.state.planets[i].pclass)
4439             proutn("   ")
4440             if game.state.planets[i].crystals != "present":
4441                 proutn(_("no "))
4442             prout(_("dilithium crystals present."))
4443             if game.state.planets[i].known=="shuttle_down":
4444                 prout(_("    Shuttle Craft Galileo on surface."))
4445     if not iknow:
4446         prout(_("No information available."))
4447
4448 def orbit():
4449     "Enter standard orbit."
4450     skip(1)
4451     scanner.chew()
4452     if game.inorbit:
4453         prout(_("Already in standard orbit."))
4454         return
4455     if damaged(DWARPEN) and damaged(DIMPULS):
4456         prout(_("Both warp and impulse engines damaged."))
4457         return
4458     if not game.plnet.is_valid():
4459         prout("There is no planet in this sector.")
4460         return
4461     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4462         prout(crmshp() + _(" not adjacent to planet."))
4463         skip(1)
4464         return
4465     game.optime = randreal(0.02, 0.05)
4466     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4467     newcnd()
4468     if consumeTime():
4469         return
4470     game.height = randreal(1400, 8600)
4471     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4472     game.inorbit = True
4473     game.ididit = True
4474
4475 def sensor():
4476     "Examine planets in this quadrant."
4477     if damaged(DSRSENS):
4478         if game.options & OPTION_TTY:
4479             prout(_("Short range sensors damaged."))
4480         return
4481     if game.iplnet is None:
4482         if game.options & OPTION_TTY:
4483             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4484         return
4485     if game.iplnet.known == "unknown":
4486         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4487         skip(1)
4488         prout(_("         Planet at Sector %s is of class %s.") %
4489               (game.plnet, game.iplnet.pclass))
4490         if game.iplnet.known=="shuttle_down":
4491             prout(_("         Sensors show Galileo still on surface."))
4492         proutn(_("         Readings indicate"))
4493         if game.iplnet.crystals != "present":
4494             proutn(_(" no"))
4495         prout(_(" dilithium crystals present.\""))
4496         if game.iplnet.known == "unknown":
4497             game.iplnet.known = "known"
4498     elif game.iplnet.inhabited:
4499         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4500         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4501
4502 def beam():
4503     "Use the transporter."
4504     nrgneed = 0
4505     scanner.chew()
4506     skip(1)
4507     if damaged(DTRANSP):
4508         prout(_("Transporter damaged."))
4509         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4510             skip(1)
4511             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4512             if ja():
4513                 shuttle()
4514         return
4515     if not game.inorbit:
4516         prout(crmshp() + _(" not in standard orbit."))
4517         return
4518     if game.shldup:
4519         prout(_("Impossible to transport through shields."))
4520         return
4521     if game.iplnet.known=="unknown":
4522         prout(_("Spock-  \"Captain, we have no information on this planet"))
4523         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4524         prout(_("  you may not go down.\""))
4525         return
4526     if not game.landed and game.iplnet.crystals=="absent":
4527         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4528         prout(_("  exploring a planet with no dilithium crystals."))
4529         proutn(_("  Are you sure this is wise?\" "))
4530         if not ja():
4531             scanner.chew()
4532             return
4533     if not (game.options & OPTION_PLAIN):
4534         nrgneed = 50 * game.skill + game.height / 100.0
4535         if nrgneed > game.energy:
4536             prout(_("Engineering to bridge--"))
4537             prout(_("  Captain, we don't have enough energy for transportation."))
4538             return
4539         if not game.landed and nrgneed * 2 > game.energy:
4540             prout(_("Engineering to bridge--"))
4541             prout(_("  Captain, we have enough energy only to transport you down to"))
4542             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4543             if game.iplnet.known == "shuttle_down":
4544                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4545             proutn(_("  Are you sure this is wise?\" "))
4546             if not ja():
4547                 scanner.chew()
4548                 return
4549     if game.landed:
4550         # Coming from planet
4551         if game.iplnet.known=="shuttle_down":
4552             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4553             if ja():
4554                 scanner.chew()
4555                 return
4556             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4557         prout(_("Landing party assembled, ready to beam up."))
4558         skip(1)
4559         prout(_("Kirk whips out communicator..."))
4560         prouts(_("BEEP  BEEP  BEEP"))
4561         skip(2)
4562         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4563     else:
4564         # Going to planet
4565         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4566         skip(1)
4567         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4568         skip(1)
4569         prout(_("Kirk-  \"Energize.\""))
4570     game.ididit = True
4571     skip(1)
4572     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4573     skip(2)
4574     if not withprob(0.98):
4575         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4576         skip(2)
4577         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4578         finish(FLOST)
4579         return
4580     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4581     game.landed = not game.landed
4582     game.energy -= nrgneed
4583     skip(2)
4584     prout(_("Transport complete."))
4585     if game.landed and game.iplnet.known=="shuttle_down":
4586         prout(_("The shuttle craft Galileo is here!"))
4587     if not game.landed and game.imine:
4588         game.icrystl = True
4589         game.cryprob = 0.05
4590     game.imine = False
4591     return
4592
4593 def mine():
4594     "Strip-mine a world for dilithium."
4595     skip(1)
4596     scanner.chew()
4597     if not game.landed:
4598         prout(_("Mining party not on planet."))
4599         return
4600     if game.iplnet.crystals == "mined":
4601         prout(_("This planet has already been strip-mined for dilithium."))
4602         return
4603     elif game.iplnet.crystals == "absent":
4604         prout(_("No dilithium crystals on this planet."))
4605         return
4606     if game.imine:
4607         prout(_("You've already mined enough crystals for this trip."))
4608         return
4609     if game.icrystl and game.cryprob == 0.05:
4610         prout(_("With all those fresh crystals aboard the ") + crmshp())
4611         prout(_("there's no reason to mine more at this time."))
4612         return
4613     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4614     if consumeTime():
4615         return
4616     prout(_("Mining operation complete."))
4617     game.iplnet.crystals = "mined"
4618     game.imine = game.ididit = True
4619
4620 def usecrystals():
4621     "Use dilithium crystals."
4622     game.ididit = False
4623     skip(1)
4624     scanner.chew()
4625     if not game.icrystl:
4626         prout(_("No dilithium crystals available."))
4627         return
4628     if game.energy >= 1000:
4629         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4630         prout(_("  except when Condition Yellow exists."))
4631         return
4632     prout(_("Spock- \"Captain, I must warn you that loading"))
4633     prout(_("  raw dilithium crystals into the ship's power"))
4634     prout(_("  system may risk a severe explosion."))
4635     proutn(_("  Are you sure this is wise?\" "))
4636     if not ja():
4637         scanner.chew()
4638         return
4639     skip(1)
4640     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4641     prout(_("  Mr. Spock and I will try it.\""))
4642     skip(1)
4643     prout(_("Spock-  \"Crystals in place, Sir."))
4644     prout(_("  Ready to activate circuit.\""))
4645     skip(1)
4646     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4647     skip(1)
4648     if withprob(game.cryprob):
4649         prouts(_("  \"Activating now! - - No good!  It's***"))
4650         skip(2)
4651         prouts(_("***RED ALERT!  RED A*L********************************"))
4652         skip(1)
4653         stars()
4654         prouts(_("******************   KA-BOOM!!!!   *******************"))
4655         skip(1)
4656         kaboom()
4657         return
4658     game.energy += randreal(5000.0, 5500.0)
4659     prouts(_("  \"Activating now! - - "))
4660     prout(_("The instruments"))
4661     prout(_("   are going crazy, but I think it's"))
4662     prout(_("   going to work!!  Congratulations, Sir!\""))
4663     game.cryprob *= 2.0
4664     game.ididit = True
4665
4666 def shuttle():
4667     "Use shuttlecraft for planetary jaunt."
4668     scanner.chew()
4669     skip(1)
4670     if damaged(DSHUTTL):
4671         if game.damage[DSHUTTL] == -1.0:
4672             if game.inorbit and game.iplnet.known == "shuttle_down":
4673                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4674             else:
4675                 prout(_("Ye Faerie Queene had no shuttle craft."))
4676         elif game.damage[DSHUTTL] > 0:
4677             prout(_("The Galileo is damaged."))
4678         else: # game.damage[DSHUTTL] < 0
4679             prout(_("Shuttle craft is now serving Big Macs."))
4680         return
4681     if not game.inorbit:
4682         prout(crmshp() + _(" not in standard orbit."))
4683         return
4684     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4685         prout(_("Shuttle craft not currently available."))
4686         return
4687     if not game.landed and game.iplnet.known=="shuttle_down":
4688         prout(_("You will have to beam down to retrieve the shuttle craft."))
4689         return
4690     if game.shldup or game.condition == "docked":
4691         prout(_("Shuttle craft cannot pass through shields."))
4692         return
4693     if game.iplnet.known=="unknown":
4694         prout(_("Spock-  \"Captain, we have no information on this planet"))
4695         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4696         prout(_("  you may not fly down.\""))
4697         return
4698     game.optime = 3.0e-5*game.height
4699     if game.optime >= 0.8*game.state.remtime:
4700         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4701         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4702                int(100*game.optime/game.state.remtime))
4703         prout(_("remaining time."))
4704         proutn(_("Are you sure this is wise?\" "))
4705         if not ja():
4706             game.optime = 0.0
4707             return
4708     if game.landed:
4709         # Kirk on planet
4710         if game.iscraft == "onship":
4711             # Galileo on ship!
4712             if not damaged(DTRANSP):
4713                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4714                 if ja():
4715                     beam()
4716                     return
4717                 proutn(_("Shuttle crew"))
4718             else:
4719                 proutn(_("Rescue party"))
4720             prout(_(" boards Galileo and swoops toward planet surface."))
4721             game.iscraft = "offship"
4722             skip(1)
4723             if consumeTime():
4724                 return
4725             game.iplnet.known="shuttle_down"
4726             prout(_("Trip complete."))
4727             return
4728         else:
4729             # Ready to go back to ship
4730             prout(_("You and your mining party board the"))
4731             prout(_("shuttle craft for the trip back to the Enterprise."))
4732             skip(1)
4733             prouts(_("The short hop begins . . ."))
4734             skip(1)
4735             game.iplnet.known="known"
4736             game.icraft = True
4737             skip(1)
4738             game.landed = False
4739             if consumeTime():
4740                 return
4741             game.iscraft = "onship"
4742             game.icraft = False
4743             if game.imine:
4744                 game.icrystl = True
4745                 game.cryprob = 0.05
4746             game.imine = False
4747             prout(_("Trip complete."))
4748             return
4749     else:
4750         # Kirk on ship and so is Galileo
4751         prout(_("Mining party assembles in the hangar deck,"))
4752         prout(_("ready to board the shuttle craft \"Galileo\"."))
4753         skip(1)
4754         prouts(_("The hangar doors open; the trip begins."))
4755         skip(1)
4756         game.icraft = True
4757         game.iscraft = "offship"
4758         if consumeTime():
4759             return
4760         game.iplnet.known = "shuttle_down"
4761         game.landed = True
4762         game.icraft = False
4763         prout(_("Trip complete."))
4764         return
4765
4766 def deathray():
4767     "Use the big zapper."
4768     game.ididit = False
4769     skip(1)
4770     scanner.chew()
4771     if game.ship != 'E':
4772         prout(_("Ye Faerie Queene has no death ray."))
4773         return
4774     if len(game.enemies)==0:
4775         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4776         return
4777     if damaged(DDRAY):
4778         prout(_("Death Ray is damaged."))
4779         return
4780     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4781     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4782     proutn(_("  are you sure this is wise?\" "))
4783     if not ja():
4784         return
4785     prout(_("Spock-  \"Acknowledged.\""))
4786     skip(1)
4787     game.ididit = True
4788     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4789     skip(1)
4790     prout(_("Crew scrambles in emergency preparation."))
4791     prout(_("Spock and Scotty ready the death ray and"))
4792     prout(_("prepare to channel all ship's power to the device."))
4793     skip(1)
4794     prout(_("Spock-  \"Preparations complete, sir.\""))
4795     prout(_("Kirk-  \"Engage!\""))
4796     skip(1)
4797     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
4798     skip(1)
4799     dprob = 0.30
4800     if game.options & OPTION_PLAIN:
4801         dprob = 0.5
4802     r = randreal()
4803     if r > dprob:
4804         prouts(_("Sulu- \"Captain!  It's working!\""))
4805         skip(2)
4806         while len(game.enemies) > 0:
4807             deadkl(game.enemies[1].location, game.quad[game.enemies[1].location.i][game.enemies[1].location.j],game.enemies[1].location)
4808         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
4809         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
4810             finish(FWON)
4811         if (game.options & OPTION_PLAIN) == 0:
4812             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
4813             if withprob(0.05):
4814                 prout(_("   is still operational.\""))
4815             else:
4816                 prout(_("   has been rendered nonfunctional.\""))
4817                 game.damage[DDRAY] = 39.95
4818         return
4819     r = randreal()        # Pick failure method
4820     if r <= 0.30:
4821         prouts(_("Sulu- \"Captain!  It's working!\""))
4822         skip(1)
4823         prouts(_("***RED ALERT!  RED ALERT!"))
4824         skip(1)
4825         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
4826         skip(1)
4827         prouts(_("***RED ALERT!  RED A*L********************************"))
4828         skip(1)
4829         stars()
4830         prouts(_("******************   KA-BOOM!!!!   *******************"))
4831         skip(1)
4832         kaboom()
4833         return
4834     if r <= 0.55:
4835         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
4836         skip(1)
4837         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
4838         skip(1)
4839         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
4840         prout(_("  have apparently been transformed into strange mutations."))
4841         prout(_("  Vulcans do not seem to be affected."))
4842         skip(1)
4843         prout(_("Kirk-  \"Raauch!  Raauch!\""))
4844         finish(FDRAY)
4845         return
4846     if r <= 0.75:
4847         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
4848         skip(2)
4849         proutn(_("Spock-  \"I believe the word is"))
4850         prouts(_(" *ASTONISHING*"))
4851         prout(_(" Mr. Sulu."))
4852         for i in range(QUADSIZE):
4853             for j in range(QUADSIZE):
4854                 if game.quad[i][j] == '.':
4855                     game.quad[i][j] = '?'
4856         prout(_("  Captain, our quadrant is now infested with"))
4857         prouts(_(" - - - - - -  *THINGS*."))
4858         skip(1)
4859         prout(_("  I have no logical explanation.\""))
4860         return
4861     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
4862     skip(1)
4863     prout(_("Scotty-  \"There are so many tribbles down here"))
4864     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
4865     finish(FTRIBBLE)
4866     return
4867
4868 # Code from reports.c begins here
4869
4870 def attackreport(curt):
4871     "eport status of bases under attack."
4872     if not curt:
4873         if is_scheduled(FCDBAS):
4874             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
4875             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
4876         elif game.isatb == 1:
4877             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
4878             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
4879         else:
4880             prout(_("No Starbase is currently under attack."))
4881     else:
4882         if is_scheduled(FCDBAS):
4883             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
4884         if game.isatb:
4885             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
4886         clreol()
4887
4888 def report():
4889     # report on general game status
4890     scanner.chew()
4891     s1 = (game.thawed and _("thawed ")) or ""
4892     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
4893     s3 = (None, _("novice"), _("fair"),
4894           _("good"), _("expert"), _("emeritus"))[game.skill]
4895     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
4896     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
4897         prout(_("No plaque is allowed."))
4898     if game.tourn:
4899         prout(_("This is tournament game %d.") % game.tourn)
4900     prout(_("Your secret password is \"%s\"") % game.passwd)
4901     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)),
4902                                                       (game.inkling + game.incom + game.inscom)))
4903     if game.incom - len(game.state.kcmdr):
4904         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
4905     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
4906         prout(_(", but no Commanders."))
4907     else:
4908         prout(".")
4909     if game.skill > SKILL_FAIR:
4910         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
4911     if len(game.state.baseq) != game.inbase:
4912         proutn(_("There "))
4913         if game.inbase-len(game.state.baseq)==1:
4914             proutn(_("has been 1 base"))
4915         else:
4916             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
4917         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
4918     else:
4919         prout(_("There are %d bases.") % game.inbase)
4920     if communicating() or game.iseenit:
4921         # Don't report this if not seen and
4922         # either the radio is dead or not at base!
4923         attackreport(False)
4924         game.iseenit = True
4925     if game.casual:
4926         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
4927     if game.nhelp:
4928         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
4929     if game.ship == 'E':
4930         proutn(_("You have "))
4931         if game.nprobes:
4932             proutn("%d" % (game.nprobes))
4933         else:
4934             proutn(_("no"))
4935         proutn(_(" deep space probe"))
4936         if game.nprobes!=1:
4937             proutn(_("s"))
4938         prout(".")
4939     if communicating() and is_scheduled(FDSPROB):
4940         if game.isarmed:
4941             proutn(_("An armed deep space probe is in "))
4942         else:
4943             proutn(_("A deep space probe is in "))
4944         prout("Quadrant %s." % game.probe.quadrant())
4945     if game.icrystl:
4946         if game.cryprob <= .05:
4947             prout(_("Dilithium crystals aboard ship... not yet used."))
4948         else:
4949             i=0
4950             ai = 0.05
4951             while game.cryprob > ai:
4952                 ai *= 2.0
4953                 i += 1
4954             prout(_("Dilithium crystals have been used %d time%s.") % \
4955                   (i, (_("s"), "")[i==1]))
4956     skip(1)
4957
4958 def lrscan(silent):
4959     "Long-range sensor scan."
4960     if damaged(DLRSENS):
4961         # Now allow base's sensors if docked
4962         if game.condition != "docked":
4963             if not silent:
4964                 prout(_("LONG-RANGE SENSORS DAMAGED."))
4965             return
4966         if not silent:
4967             prout(_("Starbase's long-range scan"))
4968     elif not silent:
4969         prout(_("Long-range scan"))
4970     for x in range(game.quadrant.i-1, game.quadrant.i+2):
4971         if not silent:
4972             proutn(" ")
4973         for y in range(game.quadrant.j-1, game.quadrant.j+2):
4974             if not Coord(x, y).valid_quadrant():
4975                 if not silent:
4976                     proutn("  -1")
4977             else:
4978                 if not damaged(DRADIO):
4979                     game.state.galaxy[x][y].charted = True
4980                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
4981                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
4982                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
4983                 if not silent and game.state.galaxy[x][y].supernova:
4984                     proutn(" ***")
4985                 elif not silent:
4986                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
4987         if not silent:
4988             prout(" ")
4989
4990 def damagereport():
4991     "Damage report."
4992     jdam = False
4993     scanner.chew()
4994     for i in range(NDEVICES):
4995         if damaged(i):
4996             if not jdam:
4997                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
4998                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
4999                 jdam = True
5000             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5001                                                game.damage[i]+0.05,
5002                                                DOCKFAC*game.damage[i]+0.005))
5003     if not jdam:
5004         prout(_("All devices functional."))
5005
5006 def rechart():
5007     "Update the chart in the Enterprise's computer from galaxy data."
5008     game.lastchart = game.state.date
5009     for i in range(GALSIZE):
5010         for j in range(GALSIZE):
5011             if game.state.galaxy[i][j].charted:
5012                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5013                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5014                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5015
5016 def chart():
5017     "Display the star chart."
5018     scanner.chew()
5019     if (game.options & OPTION_AUTOSCAN):
5020         lrscan(silent=True)
5021     if not damaged(DRADIO):
5022         rechart()
5023     if game.lastchart < game.state.date and game.condition == "docked":
5024         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5025         rechart()
5026     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5027     if game.state.date > game.lastchart:
5028         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5029     prout("      1    2    3    4    5    6    7    8")
5030     for i in range(GALSIZE):
5031         proutn("%d |" % (i+1))
5032         for j in range(GALSIZE):
5033             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5034                 proutn("<")
5035             else:
5036                 proutn(" ")
5037             if game.state.galaxy[i][j].supernova:
5038                 show = "***"
5039             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5040                 show = ".1."
5041             elif game.state.galaxy[i][j].charted:
5042                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5043             else:
5044                 show = "..."
5045             proutn(show)
5046             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5047                 proutn(">")
5048             else:
5049                 proutn(" ")
5050         proutn("  |")
5051         if i<GALSIZE:
5052             skip(1)
5053
5054 def sectscan(goodScan, i, j):
5055     "Light up an individual dot in a sector."
5056     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5057         textcolor({"green":GREEN,
5058                    "yellow":YELLOW,
5059                    "red":RED,
5060                    "docked":CYAN,
5061                    "dead":BROWN}[game.condition])
5062         if game.quad[i][j] != game.ship:
5063             highvideo()
5064         proutn("%c " % game.quad[i][j])
5065         textcolor(DEFAULT)
5066     else:
5067         proutn("- ")
5068
5069 def status(req=0):
5070     "Emit status report lines"
5071     if not req or req == 1:
5072         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5073                % (game.state.date, game.state.remtime))
5074     if not req or req == 2:
5075         if game.condition != "docked":
5076             newcnd()
5077         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5078                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5079     if not req or req == 3:
5080         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5081     if not req or req == 4:
5082         if damaged(DLIFSUP):
5083             if game.condition == "docked":
5084                 s = _("DAMAGED, Base provides")
5085             else:
5086                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5087         else:
5088             s = _("ACTIVE")
5089         prstat(_("Life Support"), s)
5090     if not req or req == 5:
5091         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5092     if not req or req == 6:
5093         extra = ""
5094         if game.icrystl and (game.options & OPTION_SHOWME):
5095             extra = _(" (have crystals)")
5096         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5097     if not req or req == 7:
5098         prstat(_("Torpedoes"), "%d" % (game.torps))
5099     if not req or req == 8:
5100         if damaged(DSHIELD):
5101             s = _("DAMAGED,")
5102         elif game.shldup:
5103             s = _("UP,")
5104         else:
5105             s = _("DOWN,")
5106         data = _(" %d%% %.1f units") \
5107                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5108         prstat(_("Shields"), s+data)
5109     if not req or req == 9:
5110         prstat(_("Klingons Left"), "%d" \
5111                % (game.state.remkl+len(game.state.kcmdr)+game.state.nscrem))
5112     if not req or req == 10:
5113         if game.options & OPTION_WORLDS:
5114             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5115             if plnet and plnet.inhabited:
5116                 prstat(_("Major system"), plnet.name)
5117             else:
5118                 prout(_("Sector is uninhabited"))
5119     elif not req or req == 11:
5120         attackreport(not req)
5121
5122 def request():
5123     "Request specified status data, a historical relic from slow TTYs."
5124     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5125     while scanner.nexttok() == "IHEOL":
5126         proutn(_("Information desired? "))
5127     scanner.chew()
5128     if scanner.token in requests:
5129         status(requests.index(scanner.token))
5130     else:
5131         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5132         prout(("  date, condition, position, lsupport, warpfactor,"))
5133         prout(("  energy, torpedoes, shields, klingons, system, time."))
5134
5135 def srscan():
5136     "Short-range scan."
5137     goodScan=True
5138     if damaged(DSRSENS):
5139         # Allow base's sensors if docked
5140         if game.condition != "docked":
5141             prout(_("   S.R. SENSORS DAMAGED!"))
5142             goodScan=False
5143         else:
5144             prout(_("  [Using Base's sensors]"))
5145     else:
5146         prout(_("     Short-range scan"))
5147     if goodScan and not damaged(DRADIO):
5148         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5149         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5150         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5151         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5152     prout("    1 2 3 4 5 6 7 8 9 10")
5153     if game.condition != "docked":
5154         newcnd()
5155     for i in range(QUADSIZE):
5156         proutn("%2d  " % (i+1))
5157         for j in range(QUADSIZE):
5158             sectscan(goodScan, i, j)
5159         skip(1)
5160
5161 def eta():
5162     "Use computer to get estimated time of arrival for a warp jump."
5163     w1 = Coord(); w2 = Coord()
5164     prompt = False
5165     if damaged(DCOMPTR):
5166         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5167         skip(1)
5168         return
5169     if scanner.nexttok() != "IHREAL":
5170         prompt = True
5171         scanner.chew()
5172         proutn(_("Destination quadrant and/or sector? "))
5173         if scanner.nexttok()!="IHREAL":
5174             huh()
5175             return
5176     w1.j = int(scanner.real-0.5)
5177     if scanner.nexttok() != "IHREAL":
5178         huh()
5179         return
5180     w1.i = int(scanner.real-0.5)
5181     if scanner.nexttok() == "IHREAL":
5182         w2.j = int(scanner.real-0.5)
5183         if scanner.nexttok() != "IHREAL":
5184             huh()
5185             return
5186         w2.i = int(scanner.real-0.5)
5187     else:
5188         if game.quadrant.j>w1.i:
5189             w2.i = 0
5190         else:
5191             w2.i=QUADSIZE-1
5192         if game.quadrant.i>w1.j:
5193             w2.j = 0
5194         else:
5195             w2.j=QUADSIZE-1
5196     if not w1.valid_quadrant() or not w2.valid_sector():
5197         huh()
5198         return
5199     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5200                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5201     wfl = False
5202     if prompt:
5203         prout(_("Answer \"no\" if you don't know the value:"))
5204     while True:
5205         scanner.chew()
5206         proutn(_("Time or arrival date? "))
5207         if scanner.nexttok()=="IHREAL":
5208             ttime = scanner.real
5209             if ttime > game.state.date:
5210                 ttime -= game.state.date # Actually a star date
5211             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5212             if ttime <= 1e-10 or twarp > 10:
5213                 prout(_("We'll never make it, sir."))
5214                 scanner.chew()
5215                 return
5216             if twarp < 1.0:
5217                 twarp = 1.0
5218             break
5219         scanner.chew()
5220         proutn(_("Warp factor? "))
5221         if scanner.nexttok()== "IHREAL":
5222             wfl = True
5223             twarp = scanner.real
5224             if twarp<1.0 or twarp > 10.0:
5225                 huh()
5226                 return
5227             break
5228         prout(_("Captain, certainly you can give me one of these."))
5229     while True:
5230         scanner.chew()
5231         ttime = (10.0*dist)/twarp**2
5232         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5233         if tpower >= game.energy:
5234             prout(_("Insufficient energy, sir."))
5235             if not game.shldup or tpower > game.energy*2.0:
5236                 if not wfl:
5237                     return
5238                 proutn(_("New warp factor to try? "))
5239                 if scanner.nexttok() == "IHREAL":
5240                     wfl = True
5241                     twarp = scanner.real
5242                     if twarp<1.0 or twarp > 10.0:
5243                         huh()
5244                         return
5245                     continue
5246                 else:
5247                     scanner.chew()
5248                     skip(1)
5249                     return
5250             prout(_("But if you lower your shields,"))
5251             proutn(_("remaining"))
5252             tpower /= 2
5253         else:
5254             proutn(_("Remaining"))
5255         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5256         if wfl:
5257             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5258         elif twarp==1.0:
5259             prout(_("Any warp speed is adequate."))
5260         else:
5261             prout(_("Minimum warp needed is %.2f,") % (twarp))
5262             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5263         if game.state.remtime < ttime:
5264             prout(_("Unfortunately, the Federation will be destroyed by then."))
5265         if twarp > 6.0:
5266             prout(_("You'll be taking risks at that speed, Captain"))
5267         if (game.isatb==1 and game.state.kscmdr == w1 and \
5268              scheduled(FSCDBAS)< ttime+game.state.date) or \
5269             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5270             prout(_("The starbase there will be destroyed by then."))
5271         proutn(_("New warp factor to try? "))
5272         if scanner.nexttok() == "IHREAL":
5273             wfl = True
5274             twarp = scanner.real
5275             if twarp<1.0 or twarp > 10.0:
5276                 huh()
5277                 return
5278         else:
5279             scanner.chew()
5280             skip(1)
5281             return
5282
5283 # Code from setup.c begins here
5284
5285 def prelim():
5286     "Issue a historically correct banner."
5287     skip(2)
5288     prout(_("-SUPER- STAR TREK"))
5289     skip(1)
5290 # From the FORTRAN original
5291 #    prout(_("Latest update-21 Sept 78"))
5292 #    skip(1)
5293
5294 def freeze(boss):
5295     "Save game."
5296     if boss:
5297         scanner.push("emsave.trk")
5298     key = scanner.nexttok()
5299     if key == "IHEOL":
5300         proutn(_("File name: "))
5301         key = scanner.nexttok()
5302     if key != "IHALPHA":
5303         huh()
5304         return
5305     if '.' not in scanner.token:
5306         scanner.token += ".trk"
5307     try:
5308         fp = open(scanner.token, "wb")
5309     except IOError:
5310         prout(_("Can't freeze game as file %s") % scanner.token)
5311         return
5312     pickle.dump(game, fp)
5313     fp.close()
5314     scanner.chew()
5315
5316 def thaw():
5317     "Retrieve saved game."
5318     global game
5319     game.passwd = None
5320     key = scanner.nexttok()
5321     if key == "IHEOL":
5322         proutn(_("File name: "))
5323         key = scanner.nexttok()
5324     if key != "IHALPHA":
5325         huh()
5326         return True
5327     if '.' not in scanner.token:
5328         scanner.token += ".trk"
5329     try:
5330         fp = open(scanner.token, "rb")
5331     except IOError:
5332         prout(_("Can't thaw game in %s") % scanner.token)
5333         return
5334     game = pickle.load(fp)
5335     fp.close()
5336     scanner.chew()
5337     return False
5338
5339 # I used <http://www.memory-alpha.org> to find planets
5340 # with references in ST:TOS.  Earth and the Alpha Centauri
5341 # Colony have been omitted.
5342 #
5343 # Some planets marked Class G and P here will be displayed as class M
5344 # because of the way planets are generated. This is a known bug.
5345 systnames = (
5346     # Federation Worlds
5347     _("Andoria (Fesoan)"),        # several episodes
5348     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5349     _("Vulcan (T'Khasi)"),        # many episodes
5350     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5351     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5352     _("Ardana"),                # TOS: "The Cloud Minders"
5353     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5354     _("Gideon"),                # TOS: "The Mark of Gideon"
5355     _("Aldebaran III"),                # TOS: "The Deadly Years"
5356     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5357     _("Altair IV"),                # TOS: "Amok Time
5358     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5359     _("Benecia"),                # TOS: "The Conscience of the King"
5360     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5361     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5362     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5363     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5364     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5365     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5366     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5367     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5368     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5369     _("Janus IV"),                # TOS: "The Devil in the Dark"
5370     _("Makus III"),                # TOS: "The Galileo Seven"
5371     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5372     _("Omega IV"),                # TOS: "The Omega Glory"
5373     _("Regulus V"),                # TOS: "Amok Time
5374     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5375     # Worlds from BSD Trek
5376     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5377     _("Beta III"),                # TOS: "The Return of the Archons"
5378     _("Triacus"),                # TOS: "And the Children Shall Lead",
5379     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5380     #        # Others
5381     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5382     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5383     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5384     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5385     #    _("Tiburon"),                # TOS: "The Way to Eden"
5386     #    _("Merak II"),                # TOS: "The Cloud Minders"
5387     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5388     #    _("Iotia"),                # TOS: "A Piece of the Action"
5389 )
5390
5391 device = (
5392     _("S. R. Sensors"), \
5393     _("L. R. Sensors"), \
5394     _("Phasers"), \
5395     _("Photon Tubes"), \
5396     _("Life Support"), \
5397     _("Warp Engines"), \
5398     _("Impulse Engines"), \
5399     _("Shields"), \
5400     _("Subspace Radio"), \
5401     _("Shuttle Craft"), \
5402     _("Computer"), \
5403     _("Navigation System"), \
5404     _("Transporter"), \
5405     _("Shield Control"), \
5406     _("Death Ray"), \
5407     _("D. S. Probe"), \
5408 )
5409
5410 def setup():
5411     "Prepare to play, set up cosmos."
5412     w = Coord()
5413     #  Decide how many of everything
5414     if choose():
5415         return # frozen game
5416     # Prepare the Enterprise
5417     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5418     game.ship = 'E'
5419     game.state.crew = FULLCREW
5420     game.energy = game.inenrg = 5000.0
5421     game.shield = game.inshld = 2500.0
5422     game.inlsr = 4.0
5423     game.lsupres = 4.0
5424     game.quadrant = randplace(GALSIZE)
5425     game.sector = randplace(QUADSIZE)
5426     game.torps = game.intorps = 10
5427     game.nprobes = randrange(2, 5)
5428     game.warpfac = 5.0
5429     for i in range(NDEVICES):
5430         game.damage[i] = 0.0
5431     # Set up assorted game parameters
5432     game.battle = Coord()
5433     game.state.date = game.indate = 100.0 * randreal(20, 51)
5434     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5435     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5436     game.isatb = game.state.nplankl = 0
5437     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5438     game.iscraft = "onship"
5439     game.landed = False
5440     game.alive = True
5441
5442     # the galaxy
5443     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5444     # the starchart
5445     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5446
5447     game.state.planets = []      # Planet information
5448     game.state.baseq = []      # Base quadrant coordinates
5449     game.state.kcmdr = []      # Commander quadrant coordinates
5450     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5451
5452     # Starchart is functional but we've never seen it
5453     game.lastchart = FOREVER
5454     # Put stars in the galaxy
5455     game.instar = 0
5456     for i in range(GALSIZE):
5457         for j in range(GALSIZE):
5458             # Can't have more stars per quadrant than fit in one decimal digit,
5459             # if we do the chart representation will break.
5460             k = randrange(1, min(10, QUADSIZE**2/10))
5461             game.instar += k
5462             game.state.galaxy[i][j].stars = k
5463     # Locate star bases in galaxy
5464     if game.idebug:
5465         prout("=== Allocating %d bases" % game.inbase)
5466     for i in range(game.inbase):
5467         while True:
5468             while True:
5469                 w = randplace(GALSIZE)
5470                 if not game.state.galaxy[w.i][w.j].starbase:
5471                     break
5472             contflag = False
5473             # C version: for (j = i-1; j > 0; j--)
5474             # so it did them in the opposite order.
5475             for j in range(1, i):
5476                 # Improved placement algorithm to spread out bases
5477                 distq = (w - game.state.baseq[j]).distance()
5478                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5479                     contflag = True
5480                     if game.idebug:
5481                         prout("=== Abandoning base #%d at %s" % (i, w))
5482                     break
5483                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5484                     if game.idebug:
5485                         prout("=== Saving base #%d, close to #%d" % (i, j))
5486             if not contflag:
5487                 break
5488         if game.idebug:
5489             prout("=== Placing base #%d in quadrant %s" % (i, w))
5490         game.state.baseq.append(w)
5491         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5492     # Position ordinary Klingon Battle Cruisers
5493     krem = game.inkling
5494     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5495     if klumper > MAXKLQUAD:
5496         klumper = MAXKLQUAD
5497     while True:
5498         r = randreal()
5499         klump = (1.0 - r*r)*klumper
5500         if klump > krem:
5501             klump = krem
5502         krem -= klump
5503         while True:
5504             w = randplace(GALSIZE)
5505             if not game.state.galaxy[w.i][w.j].supernova and \
5506                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5507                 break
5508         game.state.galaxy[w.i][w.j].klingons += int(klump)
5509         if krem <= 0:
5510             break
5511     # Position Klingon Commander Ships
5512     for i in range(game.incom):
5513         while True:
5514             w = randplace(GALSIZE)
5515             if not welcoming(w) or w in game.state.kcmdr:
5516                 continue
5517             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5518                 break
5519         game.state.galaxy[w.i][w.j].klingons += 1
5520         game.state.kcmdr.append(w)
5521     # Locate planets in galaxy
5522     for i in range(game.inplan):
5523         while True:
5524             w = randplace(GALSIZE)
5525             if game.state.galaxy[w.i][w.j].planet is None:
5526                 break
5527         new = Planet()
5528         new.quadrant = w
5529         new.crystals = "absent"
5530         if (game.options & OPTION_WORLDS) and i < NINHAB:
5531             new.pclass = "M"        # All inhabited planets are class M
5532             new.crystals = "absent"
5533             new.known = "known"
5534             new.name = systnames[i]
5535             new.inhabited = True
5536         else:
5537             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5538             if withprob(0.33):
5539                 new.crystals = "present"
5540             new.known = "unknown"
5541             new.inhabited = False
5542         game.state.galaxy[w.i][w.j].planet = new
5543         game.state.planets.append(new)
5544     # Locate Romulans
5545     for i in range(game.state.nromrem):
5546         w = randplace(GALSIZE)
5547         game.state.galaxy[w.i][w.j].romulans += 1
5548     # Place the Super-Commander if needed
5549     if game.state.nscrem > 0:
5550         while True:
5551             w = randplace(GALSIZE)
5552             if welcoming(w):
5553                 break
5554         game.state.kscmdr = w
5555         game.state.galaxy[w.i][w.j].klingons += 1
5556     # Initialize times for extraneous events
5557     schedule(FSNOVA, expran(0.5 * game.intime))
5558     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5559     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5560     schedule(FBATTAK, expran(0.3*game.intime))
5561     unschedule(FCDBAS)
5562     if game.state.nscrem:
5563         schedule(FSCMOVE, 0.2777)
5564     else:
5565         unschedule(FSCMOVE)
5566     unschedule(FSCDBAS)
5567     unschedule(FDSPROB)
5568     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5569         schedule(FDISTR, expran(1.0 + game.intime))
5570     else:
5571         unschedule(FDISTR)
5572     unschedule(FENSLV)
5573     unschedule(FREPRO)
5574     # Place thing (in tournament game, we don't want one!)
5575     # New in SST2K: never place the Thing near a starbase.
5576     # This makes sense and avoids a special case in the old code.
5577     global thing
5578     if game.tourn is None:
5579         while True:
5580             thing = randplace(GALSIZE)
5581             if thing not in game.state.baseq:
5582                 break
5583     skip(2)
5584     game.state.snap = False
5585     if game.skill == SKILL_NOVICE:
5586         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5587         prout(_("a deadly Klingon invasion force. As captain of the United"))
5588         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5589         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5590         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5591         prout(_("your mission.  As you proceed you may be given more time."))
5592         skip(1)
5593         prout(_("You will have %d supporting starbases.") % (game.inbase))
5594         proutn(_("Starbase locations-  "))
5595     else:
5596         prout(_("Stardate %d.") % int(game.state.date))
5597         skip(1)
5598         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5599         prout(_("An unknown number of Romulans."))
5600         if game.state.nscrem:
5601             prout(_("And one (GULP) Super-Commander."))
5602         prout(_("%d stardates.") % int(game.intime))
5603         proutn(_("%d starbases in ") % game.inbase)
5604     for i in range(game.inbase):
5605         proutn(repr(game.state.baseq[i]))
5606         proutn("  ")
5607     skip(2)
5608     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5609     proutn(_(" Sector %s") % game.sector)
5610     skip(2)
5611     prout(_("Good Luck!"))
5612     if game.state.nscrem:
5613         prout(_("  YOU'LL NEED IT."))
5614     waitfor()
5615     clrscr()
5616     setwnd(message_window)
5617     newqad()
5618     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5619         game.shldup = True
5620     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5621         attack(torps_ok=False)
5622
5623 def choose():
5624     "Choose your game type."
5625     while True:
5626         game.tourn = game.length = 0
5627         game.thawed = False
5628         game.skill = SKILL_NONE
5629         scanner.chew()
5630 #        if not scanner.inqueue: # Can start with command line options
5631         proutn(_("Would you like a regular, tournament, or saved game? "))
5632         scanner.nexttok()
5633         if scanner.sees("tournament"):
5634             while scanner.nexttok() == "IHEOL":
5635                 proutn(_("Type in tournament number-"))
5636             if scanner.real == 0:
5637                 scanner.chew()
5638                 continue # We don't want a blank entry
5639             game.tourn = int(round(scanner.real))
5640             random.seed(scanner.real)
5641             if logfp:
5642                 logfp.write("# random.seed(%d)\n" % scanner.real)
5643             break
5644         if scanner.sees("saved") or scanner.sees("frozen"):
5645             if thaw():
5646                 continue
5647             scanner.chew()
5648             if game.passwd is None:
5649                 continue
5650             if not game.alldone:
5651                 game.thawed = True # No plaque if not finished
5652             report()
5653             waitfor()
5654             return True
5655         if scanner.sees("regular"):
5656             break
5657         proutn(_("What is \"%s\"? ") % scanner.token)
5658         scanner.chew()
5659     while game.length==0 or game.skill==SKILL_NONE:
5660         if scanner.nexttok() == "IHALPHA":
5661             if scanner.sees("short"):
5662                 game.length = 1
5663             elif scanner.sees("medium"):
5664                 game.length = 2
5665             elif scanner.sees("long"):
5666                 game.length = 4
5667             elif scanner.sees("novice"):
5668                 game.skill = SKILL_NOVICE
5669             elif scanner.sees("fair"):
5670                 game.skill = SKILL_FAIR
5671             elif scanner.sees("good"):
5672                 game.skill = SKILL_GOOD
5673             elif scanner.sees("expert"):
5674                 game.skill = SKILL_EXPERT
5675             elif scanner.sees("emeritus"):
5676                 game.skill = SKILL_EMERITUS
5677             else:
5678                 proutn(_("What is \""))
5679                 proutn(scanner.token)
5680                 prout("\"?")
5681         else:
5682             scanner.chew()
5683             if game.length==0:
5684                 proutn(_("Would you like a Short, Medium, or Long game? "))
5685             elif game.skill == SKILL_NONE:
5686                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5687     # Choose game options -- added by ESR for SST2K
5688     if scanner.nexttok() != "IHALPHA":
5689         scanner.chew()
5690         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5691         scanner.nexttok()
5692     if scanner.sees("plain"):
5693         # Approximates the UT FORTRAN version.
5694         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5695         game.options |= OPTION_PLAIN
5696     elif scanner.sees("almy"):
5697         # Approximates Tom Almy's version.
5698         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5699         game.options |= OPTION_ALMY
5700     elif scanner.sees("fancy") or scanner.sees("\n"):
5701         pass
5702     elif len(scanner.token):
5703         proutn(_("What is \"%s\"?") % scanner.token)
5704     game.options &=~ OPTION_COLOR
5705     setpassword()
5706     if game.passwd == "debug":
5707         game.idebug = True
5708         prout("=== Debug mode enabled.")
5709     # Use parameters to generate initial values of things
5710     game.damfac = 0.5 * game.skill
5711     game.inbase = randrange(BASEMIN, BASEMAX+1)
5712     game.inplan = 0
5713     if game.options & OPTION_PLANETS:
5714         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5715     if game.options & OPTION_WORLDS:
5716         game.inplan += int(NINHAB)
5717     game.state.nromrem = game.inrom = randrange(2 *game.skill)
5718     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5719     game.state.remtime = 7.0 * game.length
5720     game.intime = game.state.remtime
5721     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5722     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5723     game.state.remres = (game.inkling+4*game.incom)*game.intime
5724     game.inresor = game.state.remres
5725     if game.inkling > 50:
5726         game.inbase += 1
5727     return False
5728
5729 def dropin(iquad=None):
5730     "Drop a feature on a random dot in the current quadrant."
5731     while True:
5732         w = randplace(QUADSIZE)
5733         if game.quad[w.i][w.j] == '.':
5734             break
5735     if iquad is not None:
5736         game.quad[w.i][w.j] = iquad
5737     return w
5738
5739 def newcnd():
5740     "Update our alert status."
5741     game.condition = "green"
5742     if game.energy < 1000.0:
5743         game.condition = "yellow"
5744     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5745         game.condition = "red"
5746     if not game.alive:
5747         game.condition="dead"
5748
5749 def newkling():
5750     "Drop new Klingon into current quadrant."
5751     return Enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
5752
5753 def sortenemies():
5754     "Sort enemies by distance so 'nearest' is meaningful."
5755     game.enemies.sort(key=lambda x: x.kdist)
5756
5757 def newqad():
5758     "Set up a new state of quadrant, for when we enter or re-enter it."
5759     game.justin = True
5760     game.iplnet = None
5761     game.neutz = game.inorbit = game.landed = False
5762     game.ientesc = game.iseenit = False
5763     # Create a blank quadrant
5764     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
5765     if game.iscate:
5766         # Attempt to escape Super-commander, so tbeam back!
5767         game.iscate = False
5768         game.ientesc = True
5769     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
5770     # cope with supernova
5771     if q.supernova:
5772         return
5773     game.klhere = q.klingons
5774     game.irhere = q.romulans
5775     # Position Starship
5776     game.quad[game.sector.i][game.sector.j] = game.ship
5777     game.enemies = []
5778     if q.klingons:
5779         # Position ordinary Klingons
5780         for _i in range(game.klhere):
5781             newkling()
5782         # If we need a commander, promote a Klingon
5783         for cmdr in game.state.kcmdr:
5784             if cmdr == game.quadrant:
5785                 e = game.enemies[game.klhere-1]
5786                 game.quad[e.location.i][e.location.j] = 'C'
5787                 e.power = randreal(950,1350) + 50.0*game.skill
5788                 break
5789         # If we need a super-commander, promote a Klingon
5790         if game.quadrant == game.state.kscmdr:
5791             e = game.enemies[0]
5792             game.quad[e.location.i][e.location.j] = 'S'
5793             e.power = randreal(1175.0,  1575.0) + 125.0*game.skill
5794             game.iscate = (game.state.remkl > 1)
5795     # Put in Romulans if needed
5796     for _i in range(q.romulans):
5797         Enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
5798     # If quadrant needs a starbase, put it in
5799     if q.starbase:
5800         game.base = dropin('B')
5801     # If quadrant needs a planet, put it in
5802     if q.planet:
5803         game.iplnet = q.planet
5804         if not q.planet.inhabited:
5805             game.plnet = dropin('P')
5806         else:
5807             game.plnet = dropin('@')
5808     # Check for condition
5809     newcnd()
5810     # Check for RNZ
5811     if game.irhere > 0 and game.klhere == 0:
5812         game.neutz = True
5813         if not damaged(DRADIO):
5814             skip(1)
5815             prout(_("LT. Uhura- \"Captain, an urgent message."))
5816             prout(_("  I'll put it on audio.\"  CLICK"))
5817             skip(1)
5818             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
5819             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
5820     # Put in THING if needed
5821     if thing == game.quadrant:
5822         Enemy(etype='?', loc=dropin(),
5823               power=randreal(6000,6500.0)+250.0*game.skill)
5824         if not damaged(DSRSENS):
5825             skip(1)
5826             prout(_("Mr. Spock- \"Captain, this is most unusual."))
5827             prout(_("    Please examine your short-range scan.\""))
5828     # Decide if quadrant needs a Tholian; lighten up if skill is low
5829     if game.options & OPTION_THOLIAN:
5830         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
5831             (game.skill == SKILL_GOOD and withprob(0.05)) or \
5832             (game.skill > SKILL_GOOD and withprob(0.08)):
5833             w = Coord()
5834             while True:
5835                 w.i = withprob(0.5) * (QUADSIZE-1)
5836                 w.j = withprob(0.5) * (QUADSIZE-1)
5837                 if game.quad[w.i][w.j] == '.':
5838                     break
5839             game.tholian = Enemy(etype='T', loc=w,
5840                                  power=randrange(100, 500) + 25.0*game.skill)
5841             # Reserve unoccupied corners
5842             if game.quad[0][0]=='.':
5843                 game.quad[0][0] = 'X'
5844             if game.quad[0][QUADSIZE-1]=='.':
5845                 game.quad[0][QUADSIZE-1] = 'X'
5846             if game.quad[QUADSIZE-1][0]=='.':
5847                 game.quad[QUADSIZE-1][0] = 'X'
5848             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
5849                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
5850     sortenemies()
5851     # And finally the stars
5852     for _i in range(q.stars):
5853         dropin('*')
5854     # Put in a few black holes
5855     for _i in range(1, 3+1):
5856         if withprob(0.5):
5857             dropin(' ')
5858     # Take out X's in corners if Tholian present
5859     if game.tholian:
5860         if game.quad[0][0]=='X':
5861             game.quad[0][0] = '.'
5862         if game.quad[0][QUADSIZE-1]=='X':
5863             game.quad[0][QUADSIZE-1] = '.'
5864         if game.quad[QUADSIZE-1][0]=='X':
5865             game.quad[QUADSIZE-1][0] = '.'
5866         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
5867             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
5868
5869 def setpassword():
5870     "Set the self-destruct password."
5871     if game.options & OPTION_PLAIN:
5872         while True:
5873             scanner.chew()
5874             proutn(_("Please type in a secret password- "))
5875             scanner.nexttok()
5876             game.passwd = scanner.token
5877             if game.passwd != None:
5878                 break
5879     else:
5880         game.passwd = ""
5881         game.passwd += chr(ord('a')+randrange(26))
5882         game.passwd += chr(ord('a')+randrange(26))
5883         game.passwd += chr(ord('a')+randrange(26))
5884
5885 # Code from sst.c begins here
5886
5887 commands = [
5888     ("SRSCAN",           OPTION_TTY),
5889     ("STATUS",           OPTION_TTY),
5890     ("REQUEST",          OPTION_TTY),
5891     ("LRSCAN",           OPTION_TTY),
5892     ("PHASERS",          0),
5893     ("TORPEDO",          0),
5894     ("PHOTONS",          0),
5895     ("MOVE",             0),
5896     ("SHIELDS",          0),
5897     ("DOCK",             0),
5898     ("DAMAGES",          0),
5899     ("CHART",            0),
5900     ("IMPULSE",          0),
5901     ("REST",             0),
5902     ("WARP",             0),
5903     ("SCORE",            0),
5904     ("SENSORS",          OPTION_PLANETS),
5905     ("ORBIT",            OPTION_PLANETS),
5906     ("TRANSPORT",        OPTION_PLANETS),
5907     ("MINE",             OPTION_PLANETS),
5908     ("CRYSTALS",         OPTION_PLANETS),
5909     ("SHUTTLE",          OPTION_PLANETS),
5910     ("PLANETS",          OPTION_PLANETS),
5911     ("REPORT",           0),
5912     ("COMPUTER",         0),
5913     ("COMMANDS",         0),
5914     ("EMEXIT",           0),
5915     ("PROBE",            OPTION_PROBE),
5916     ("SAVE",             0),
5917     ("FREEZE",           0),        # Synonym for SAVE
5918     ("ABANDON",          0),
5919     ("DESTRUCT",         0),
5920     ("DEATHRAY",         0),
5921     ("DEBUG",            0),
5922     ("MAYDAY",           0),
5923     ("SOS",              0),        # Synonym for MAYDAY
5924     ("CALL",             0),        # Synonym for MAYDAY
5925     ("QUIT",             0),
5926     ("HELP",             0),
5927     ("",                 0),
5928 ]
5929
5930 def listCommands():
5931     "Generate a list of legal commands."
5932     prout(_("LEGAL COMMANDS ARE:"))
5933     emitted = 0
5934     for (key, opt) in commands:
5935         if not opt or (opt & game.options):
5936             proutn("%-12s " % key)
5937             emitted += 1
5938             if emitted % 5 == 4:
5939                 skip(1)
5940     skip(1)
5941
5942 def helpme():
5943     "Browse on-line help."
5944     key = scanner.nexttok()
5945     while True:
5946         if key == "IHEOL":
5947             setwnd(prompt_window)
5948             proutn(_("Help on what command? "))
5949             key = scanner.nexttok()
5950         setwnd(message_window)
5951         if key == "IHEOL":
5952             return
5953         cmds = [x[0] for x in commands]
5954         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
5955             break
5956         skip(1)
5957         listCommands()
5958         key = "IHEOL"
5959         scanner.chew()
5960         skip(1)
5961     cmd = scanner.token.upper()
5962     for directory in docpath:
5963         try:
5964             fp = open(os.path.join(directory, "sst.doc"), "r")
5965             break
5966         except IOError:
5967             pass
5968     else:
5969         prout(_("Spock-  \"Captain, that information is missing from the"))
5970         prout(_("   computer. You need to find sst.doc and put it somewhere"))
5971         proutn(_("   in these directories: %s") % ":".join(docpath))
5972         prout(".\"")
5973         # This used to continue: "You need to find SST.DOC and put
5974         # it in the current directory."
5975         return
5976     while True:
5977         linebuf = fp.readline()
5978         if linebuf == '':
5979             prout(_("Spock- \"Captain, there is no information on that command.\""))
5980             fp.close()
5981             return
5982         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
5983             linebuf = linebuf[3:].strip()
5984             if cmd.upper() == linebuf:
5985                 break
5986     skip(1)
5987     prout(_("Spock- \"Captain, I've found the following information:\""))
5988     skip(1)
5989     while True:
5990         linebuf = fp.readline()
5991         if "******" in linebuf:
5992             break
5993         proutn(linebuf)
5994     fp.close()
5995
5996 def makemoves():
5997     "Command-interpretation loop."
5998     while True:         # command loop
5999         drawmaps(1)
6000         while True:        # get a command
6001             hitme = False
6002             game.optime = game.justin = False
6003             scanner.chew()
6004             setwnd(prompt_window)
6005             clrscr()
6006             proutn("COMMAND> ")
6007             if scanner.nexttok() == "IHEOL":
6008                 if game.options & OPTION_CURSES:
6009                     makechart()
6010                 continue
6011             elif scanner.token == "":
6012                 continue
6013             game.ididit = False
6014             clrscr()
6015             setwnd(message_window)
6016             clrscr()
6017             abandon_passed = False
6018             cmd = ""    # Force cmd to persist after loop
6019             opt = 0     # Force opt to persist after loop
6020             for (cmd, opt) in commands:
6021                 # commands after ABANDON cannot be abbreviated
6022                 if cmd == "ABANDON":
6023                     abandon_passed = True
6024                 if cmd == scanner.token.upper() or (not abandon_passed \
6025                         and cmd.startswith(scanner.token.upper())):
6026                     break
6027             if cmd == "":
6028                 listCommands()
6029                 continue
6030             elif opt and not (opt & game.options):
6031                 huh()
6032             else:
6033                 break
6034         if cmd == "SRSCAN":                # srscan
6035             srscan()
6036         elif cmd == "STATUS":                # status
6037             status()
6038         elif cmd == "REQUEST":                # status request
6039             request()
6040         elif cmd == "LRSCAN":                # long range scan
6041             lrscan(silent=False)
6042         elif cmd == "PHASERS":                # phasers
6043             phasers()
6044             if game.ididit:
6045                 hitme = True
6046         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6047             torps()
6048             if game.ididit:
6049                 hitme = True
6050         elif cmd == "MOVE":                # move under warp
6051             warp(wcourse=None, involuntary=False)
6052         elif cmd == "SHIELDS":                # shields
6053             doshield(shraise=False)
6054             if game.ididit:
6055                 hitme = True
6056                 game.shldchg = False
6057         elif cmd == "DOCK":                # dock at starbase
6058             dock(True)
6059             if game.ididit:
6060                 attack(torps_ok=False)
6061         elif cmd == "DAMAGES":                # damage reports
6062             damagereport()
6063         elif cmd == "CHART":                # chart
6064             makechart()
6065         elif cmd == "IMPULSE":                # impulse
6066             impulse()
6067         elif cmd == "REST":                # rest
6068             wait()
6069             if game.ididit:
6070                 hitme = True
6071         elif cmd == "WARP":                # warp
6072             setwarp()
6073         elif cmd == "SCORE":                # score
6074             score()
6075         elif cmd == "SENSORS":                # sensors
6076             sensor()
6077         elif cmd == "ORBIT":                # orbit
6078             orbit()
6079             if game.ididit:
6080                 hitme = True
6081         elif cmd == "TRANSPORT":                # transport "beam"
6082             beam()
6083         elif cmd == "MINE":                # mine
6084             mine()
6085             if game.ididit:
6086                 hitme = True
6087         elif cmd == "CRYSTALS":                # crystals
6088             usecrystals()
6089             if game.ididit:
6090                 hitme = True
6091         elif cmd == "SHUTTLE":                # shuttle
6092             shuttle()
6093             if game.ididit:
6094                 hitme = True
6095         elif cmd == "PLANETS":                # Planet list
6096             survey()
6097         elif cmd == "REPORT":                # Game Report
6098             report()
6099         elif cmd == "COMPUTER":                # use COMPUTER!
6100             eta()
6101         elif cmd == "COMMANDS":
6102             listCommands()
6103         elif cmd == "EMEXIT":                # Emergency exit
6104             clrscr()                        # Hide screen
6105             freeze(True)                # forced save
6106             raise SystemExit(1)                # And quick exit
6107         elif cmd == "PROBE":
6108             probe()                        # Launch probe
6109             if game.ididit:
6110                 hitme = True
6111         elif cmd == "ABANDON":                # Abandon Ship
6112             abandon()
6113         elif cmd == "DESTRUCT":                # Self Destruct
6114             selfdestruct()
6115         elif cmd == "SAVE":                # Save Game
6116             freeze(False)
6117             clrscr()
6118             if game.skill > SKILL_GOOD:
6119                 prout(_("WARNING--Saved games produce no plaques!"))
6120         elif cmd == "DEATHRAY":                # Try a desparation measure
6121             deathray()
6122             if game.ididit:
6123                 hitme = True
6124         elif cmd == "DEBUGCMD":                # What do we want for debug???
6125             debugme()
6126         elif cmd == "MAYDAY":                # Call for help
6127             mayday()
6128             if game.ididit:
6129                 hitme = True
6130         elif cmd == "QUIT":
6131             game.alldone = True                # quit the game
6132         elif cmd == "HELP":
6133             helpme()                        # get help
6134         while True:
6135             if game.alldone:
6136                 break                # Game has ended
6137             if game.optime != 0.0:
6138                 events()
6139                 if game.alldone:
6140                     break        # Events did us in
6141             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6142                 atover(False)
6143                 continue
6144             if hitme and not game.justin:
6145                 attack(torps_ok=True)
6146                 if game.alldone:
6147                     break
6148                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6149                     atover(False)
6150                     hitme = True
6151                     continue
6152             break
6153         if game.alldone:
6154             break
6155     if game.idebug:
6156         prout("=== Ending")
6157
6158 def cramen(ch):
6159     "Emit the name of an enemy or feature."
6160     if   ch == 'R': s = _("Romulan")
6161     elif ch == 'K': s = _("Klingon")
6162     elif ch == 'C': s = _("Commander")
6163     elif ch == 'S': s = _("Super-commander")
6164     elif ch == '*': s = _("Star")
6165     elif ch == 'P': s = _("Planet")
6166     elif ch == 'B': s = _("Starbase")
6167     elif ch == ' ': s = _("Black hole")
6168     elif ch == 'T': s = _("Tholian")
6169     elif ch == '#': s = _("Tholian web")
6170     elif ch == '?': s = _("Stranger")
6171     elif ch == '@': s = _("Inhabited World")
6172     else: s = "Unknown??"
6173     return s
6174
6175 def crmena(loud, enemy, loctype, w):
6176     "Emit the name of an enemy and his location."
6177     buf = ""
6178     if loud:
6179         buf += "***"
6180     buf += cramen(enemy) + _(" at ")
6181     if loctype == "quadrant":
6182         buf += _("Quadrant ")
6183     elif loctype == "sector":
6184         buf += _("Sector ")
6185     return buf + repr(w)
6186
6187 def crmshp():
6188     "Emit our ship name."
6189     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6190
6191 def stars():
6192     "Emit a line of stars"
6193     prouts("******************************************************")
6194     skip(1)
6195
6196 def expran(avrage):
6197     return -avrage*math.log(1e-7 + randreal())
6198
6199 def randplace(size):
6200     "Choose a random location."
6201     w = Coord()
6202     w.i = randrange(size)
6203     w.j = randrange(size)
6204     return w
6205
6206 class sstscanner:
6207     def __init__(self):
6208         self.type = None
6209         self.token = None
6210         self.real = 0.0
6211         self.inqueue = []
6212     def nexttok(self):
6213         # Get a token from the user
6214         self.real = 0.0
6215         self.token = ''
6216         # Fill the token quue if nothing here
6217         while not self.inqueue:
6218             sline = cgetline()
6219             if curwnd==prompt_window:
6220                 clrscr()
6221                 setwnd(message_window)
6222                 clrscr()
6223             if sline == '':
6224                 return None
6225             if not sline:
6226                 continue
6227             else:
6228                 self.inqueue = sline.lstrip().split() + ["\n"]
6229         # From here on in it's all looking at the queue
6230         self.token = self.inqueue.pop(0)
6231         if self.token == "\n":
6232             self.type = "IHEOL"
6233             return "IHEOL"
6234         try:
6235             self.real = float(self.token)
6236             self.type = "IHREAL"
6237             return "IHREAL"
6238         except ValueError:
6239             pass
6240         # Treat as alpha
6241         self.token = self.token.lower()
6242         self.type = "IHALPHA"
6243         self.real = None
6244         return "IHALPHA"
6245     def append(self, tok):
6246         self.inqueue.append(tok)
6247     def push(self, tok):
6248         self.inqueue.insert(0, tok)
6249     def waiting(self):
6250         return self.inqueue
6251     def chew(self):
6252         # Demand input for next scan
6253         self.inqueue = []
6254         self.real = self.token = None
6255     def sees(self, s):
6256         # compares s to item and returns true if it matches to the length of s
6257         return s.startswith(self.token)
6258     def int(self):
6259         # Round token value to nearest integer
6260         return int(round(self.real))
6261     def getcoord(self):
6262         s = Coord()
6263         self.nexttok()
6264         if self.type != "IHREAL":
6265             huh()
6266             return None
6267         s.i = self.int()-1
6268         self.nexttok()
6269         if self.type != "IHREAL":
6270             huh()
6271             return None
6272         s.j = self.int()-1
6273         return s
6274     def __repr__(self):
6275         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6276
6277 def ja():
6278     "Yes-or-no confirmation."
6279     scanner.chew()
6280     while True:
6281         scanner.nexttok()
6282         if scanner.token == 'y':
6283             return True
6284         if scanner.token == 'n':
6285             return False
6286         scanner.chew()
6287         proutn(_("Please answer with \"y\" or \"n\": "))
6288
6289 def huh():
6290     "Complain about unparseable input."
6291     scanner.chew()
6292     skip(1)
6293     prout(_("Beg your pardon, Captain?"))
6294
6295 def debugme():
6296     "Access to the internals for debugging."
6297     proutn("Reset levels? ")
6298     if ja():
6299         if game.energy < game.inenrg:
6300             game.energy = game.inenrg
6301         game.shield = game.inshld
6302         game.torps = game.intorps
6303         game.lsupres = game.inlsr
6304     proutn("Reset damage? ")
6305     if ja():
6306         for i in range(NDEVICES):
6307             if game.damage[i] > 0.0:
6308                 game.damage[i] = 0.0
6309     proutn("Toggle debug flag? ")
6310     if ja():
6311         game.idebug = not game.idebug
6312         if game.idebug:
6313             prout("Debug output ON")
6314         else:
6315             prout("Debug output OFF")
6316     proutn("Cause selective damage? ")
6317     if ja():
6318         for i in range(NDEVICES):
6319             proutn("Kill %s?" % device[i])
6320             scanner.chew()
6321             key = scanner.nexttok()
6322             if key == "IHALPHA" and scanner.sees("y"):
6323                 game.damage[i] = 10.0
6324     proutn("Examine/change events? ")
6325     if ja():
6326         ev = Event()
6327         w = Coord()
6328         legends = {
6329             FSNOVA:  "Supernova       ",
6330             FTBEAM:  "T Beam          ",
6331             FSNAP:   "Snapshot        ",
6332             FBATTAK: "Base Attack     ",
6333             FCDBAS:  "Base Destroy    ",
6334             FSCMOVE: "SC Move         ",
6335             FSCDBAS: "SC Base Destroy ",
6336             FDSPROB: "Probe Move      ",
6337             FDISTR:  "Distress Call   ",
6338             FENSLV:  "Enslavement     ",
6339             FREPRO:  "Klingon Build   ",
6340         }
6341         for i in range(1, NEVENTS):
6342             proutn(legends[i])
6343             if is_scheduled(i):
6344                 proutn("%.2f" % (scheduled(i)-game.state.date))
6345                 if i == FENSLV or i == FREPRO:
6346                     ev = findevent(i)
6347                     proutn(" in %s" % ev.quadrant)
6348             else:
6349                 proutn("never")
6350             proutn("? ")
6351             scanner.chew()
6352             key = scanner.nexttok()
6353             if key == 'n':
6354                 unschedule(i)
6355                 scanner.chew()
6356             elif key == "IHREAL":
6357                 ev = schedule(i, scanner.real)
6358                 if i == FENSLV or i == FREPRO:
6359                     scanner.chew()
6360                     proutn("In quadrant- ")
6361                     key = scanner.nexttok()
6362                     # "IHEOL" says to leave coordinates as they are
6363                     if key != "IHEOL":
6364                         if key != "IHREAL":
6365                             prout("Event %d canceled, no x coordinate." % (i))
6366                             unschedule(i)
6367                             continue
6368                         w.i = int(round(scanner.real))
6369                         key = scanner.nexttok()
6370                         if key != "IHREAL":
6371                             prout("Event %d canceled, no y coordinate." % (i))
6372                             unschedule(i)
6373                             continue
6374                         w.j = int(round(scanner.real))
6375                         ev.quadrant = w
6376         scanner.chew()
6377     proutn("Induce supernova here? ")
6378     if ja():
6379         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6380         atover(True)
6381
6382 if __name__ == '__main__':
6383     try:
6384         #global line, thing, game
6385         game = None
6386         thing = Thingy()
6387         game = Gamestate()
6388         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6389         if os.getenv("TERM"):
6390             game.options |= OPTION_CURSES
6391         else:
6392             game.options |= OPTION_TTY
6393         seed = int(time.time())
6394         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:txV")
6395         replay = False
6396         for (switch, val) in options:
6397             if switch == '-r':
6398                 try:
6399                     replayfp = open(val, "r")
6400                 except IOError:
6401                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6402                     raise SystemExit(1)
6403                 try:
6404                     line = replayfp.readline().strip()
6405                     (leader, __, seed) = line.split()
6406                     seed = eval(seed)
6407                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6408                     line = replayfp.readline().strip()
6409                     arguments += line.split()[2:]
6410                     replay = True
6411                 except ValueError:
6412                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6413                     raise SystemExit(1)
6414                 game.options |= OPTION_TTY
6415                 game.options &=~ OPTION_CURSES
6416             elif switch == '-s':
6417                 seed = int(val)
6418             elif switch == '-t':
6419                 game.options |= OPTION_TTY
6420                 game.options &=~ OPTION_CURSES
6421             elif switch == '-x':
6422                 game.idebug = True
6423             elif switch == '-V':
6424                 print("SST2K", version)
6425                 raise SystemExit(0)
6426             else:
6427                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6428                 raise SystemExit(1)
6429         # where to save the input in case of bugs
6430         if "TMPDIR" in os.environ:
6431             tmpdir = os.environ['TMPDIR']
6432         else:
6433             tmpdir = "/tmp"
6434         try:
6435             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6436         except IOError:
6437             sys.stderr.write("sst: warning, can't open logfile\n")
6438             sys.exit(1)
6439         if logfp:
6440             logfp.write("# seed %s\n" % seed)
6441             logfp.write("# options %s\n" % " ".join(arguments))
6442             logfp.write("# SST2K version %s\n" % version)
6443             logfp.write("# recorded by %s@%s on %s\n" % \
6444                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6445         random.seed(seed)
6446         scanner = sstscanner()
6447         for arg in arguments:
6448             scanner.append(arg)
6449         try:
6450             iostart()
6451             while True: # Play a game
6452                 setwnd(fullscreen_window)
6453                 clrscr()
6454                 prelim()
6455                 setup()
6456                 if game.alldone:
6457                     score()
6458                     game.alldone = False
6459                 else:
6460                     makemoves()
6461                 if replay:
6462                     break
6463                 skip(1)
6464                 stars()
6465                 skip(1)
6466                 if game.tourn and game.alldone:
6467                     proutn(_("Do you want your score recorded?"))
6468                     if ja():
6469                         scanner.chew()
6470                         scanner.push("\n")
6471                         freeze(False)
6472                 scanner.chew()
6473                 proutn(_("Do you want to play again? "))
6474                 if not ja():
6475                     break
6476             skip(1)
6477             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6478         finally:
6479             ioend()
6480         raise SystemExit(0)
6481     except KeyboardInterrupt:
6482         if logfp:
6483             logfp.close()
6484         print("")
6485
6486 # End.