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