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