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