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