Game log opening stanzas have an end delimiter.
[super-star-trek.git] / sst
1 #!/usr/bin/env python3
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 # Copyright by Eric S. Raymond
15 # SPDX-License-Identifier: BSD-2-clause
16
17 # pylint: disable=line-too-long,superfluous-parens,too-many-lines,invalid-name,missing-function-docstring,missing-class-docstring,multiple-statements,too-many-branches,too-many-statements,too-many-locals,too-many-nested-blocks,too-many-return-statements,too-many-instance-attributes,global-statement,no-else-break,no-else-return,no-else-continue,too-few-public-methods,too-many-boolean-expressions,consider-using-f-string,consider-using-enumerate,consider-using-with,unspecified-encoding
18
19 # pylint: disable=multiple-imports
20 import os, sys, math, curses, time, pickle, copy, gettext, getpass
21 import getopt, socket
22 import codecs
23
24 # This import only works on Unixes.  The intention is to enable
25 # Ctrl-P, Ctrl-N, and friends in Cmd.
26 try:
27     # pylint: disable=unused-import
28     import readline
29 except ImportError:  # pragma: no cover
30     pass
31
32 version = "2.7"
33
34 docpath         = (".", "doc/", "/usr/share/doc/sst/")
35
36 def _(st):
37     return gettext.gettext(st)
38
39 # Rolling our own LCG because Python changed its incompatibly in 3.2.
40 # Thus, we needed to have our own to be 2/3 polyglot; it will be
41 # helpful when and if we ever forward-port to another language.
42
43 class randomizer:
44     # LCG PRNG parameters tested against
45     # Knuth vol. 2. by the authors of ADVENT
46     LCG_A = 1093
47     LCG_C = 221587
48     LCG_M = 1048576
49
50     @staticmethod
51     def random():
52         old_x = game.lcg_x
53         game.lcg_x = (randomizer.LCG_A * game.lcg_x + randomizer.LCG_C) % randomizer.LCG_M
54         return old_x / randomizer.LCG_M
55
56     @staticmethod
57     def withprob(p):
58         v = randomizer.random()
59         #if logfp:
60         #    logfp.write("#withprob(%.2f) -> %s\n" % (p, v < p))
61         return v < p
62
63     @staticmethod
64     def integer(*args):
65         v = randomizer.random()
66         if len(args) == 1:
67             v = int(v * args[0])
68         else:
69             v = args[0] + int(v * (args[1] - args[0]))
70         #if logfp:
71         #    logfp.write("#integer%s -> %s\n" % (args, v))
72         return int(v)
73
74     @staticmethod
75     def real(*args):
76         v = randomizer.random()
77         if len(args) == 1:
78             v *= args[0]                 # from [0, args[0])
79         elif len(args) == 2:
80             v = args[0] + v*(args[1]-args[0])        # from [args[0], args[1])
81         #if logfp:
82         #    logfp.write("#real%s -> %f\n" % (args, v))
83         return v
84
85     @staticmethod
86     def seed(n):
87         #if logfp:
88         #    logfp.write("#seed(%d)\n" % n)
89         game.lcg_x = n % randomizer.LCG_M
90
91     @staticmethod
92     def getrngstate():
93         return game.lcg_x
94
95     @staticmethod
96     def setrngstate(n):
97         game.lcg_x = n
98
99 GALSIZE         = 8             # Galaxy size in quadrants
100 NINHAB          = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
101 MAXUNINHAB      = 10            # Maximum uninhabited worlds
102 QUADSIZE        = 10            # Quadrant size in sectors
103 BASEMIN         = 2                             # Minimum starbases
104 BASEMAX         = (GALSIZE * GALSIZE // 12)     # Maximum starbases
105 MAXKLGAME       = 127           # Maximum Klingons per game
106 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
107 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong
108 FOREVER         = 1e30          # Time for the indefinite future
109 MAXBURST        = 3             # Max # of torps you can launch in one turn
110 MINCMDR         = 10            # Minimum number of Klingon commanders
111 DOCKFAC         = 0.25          # Repair faster when docked
112 PHASEFAC        = 2.0           # Phaser attenuation factor
113
114 ALGERON         = 2311          # Date of the Treaty of Algeron
115
116
117 DEFAULT      = -1
118 BLACK        = 0
119 BLUE         = 1
120 GREEN        = 2
121 CYAN         = 3
122 RED          = 4
123 MAGENTA      = 5
124 BROWN        = 6
125 LIGHTGRAY    = 7
126 DARKGRAY     = 8
127 LIGHTBLUE    = 9
128 LIGHTGREEN   = 10
129 LIGHTCYAN    = 11
130 LIGHTRED     = 12
131 LIGHTMAGENTA = 13
132 YELLOW       = 14
133 WHITE        = 15
134
135 class TrekError(Exception):
136     pass
137
138 class JumpOut(Exception):
139     pass
140
141 def letterize(n):
142     return chr(ord('a') + n - 1)
143
144 class Coord:
145     def __init__(self, x=None, y=None):
146         self.i = x      # Row
147         self.j = y      # Column
148     def valid_quadrant(self):
149         return (self.i is not None) and (self.j is not None) and (self.i >= 0) and (self.i < GALSIZE) and (self.j >= 0) and (self.j < GALSIZE)
150     def valid_sector(self):
151         return (self.i is not None) and (self.j is not None) and (self.i >= 0) and (self.i < QUADSIZE) and (self.j >= 0) and (self.j < QUADSIZE)
152     def invalidate(self):
153         self.i = self.j = None
154     def __eq__(self, other):
155         return other is not None and self.i == other.i and self.j == other.j
156     def __ne__(self, other):
157         return other is None or self.i != other.i or self.j != other.j
158     def __add__(self, other):
159         return Coord(self.i+other.i, self.j+other.j)
160     def __sub__(self, other):
161         return Coord(self.i-other.i, self.j-other.j)
162     def __mul__(self, other):
163         return Coord(self.i*other, self.j*other)
164     def __rmul__(self, other):
165         return Coord(self.i*other, self.j*other)
166     def __div__(self, other):  # pragma: no cover
167         return Coord(self.i/other, self.j/other)
168     def __truediv__(self, other):  # pragma: no cover
169         return Coord(self.i/other, self.j/other)
170     def __floordiv__(self, other):  # pragma: no cover
171         return Coord(self.i//other, self.j//other)
172     def __mod__(self, other):
173         return Coord(self.i % other, self.j % other)
174     def __rtruediv__(self, other):  # pragma: no cover
175         return Coord(self.i/other, self.j/other)
176     def __rfloordiv__(self, other):  # pragma: no cover
177         return Coord(self.i//other, self.j//other)
178     def roundtogrid(self):
179         return Coord(int(round(self.i)), int(round(self.j)))
180     def distance(self, other=None):
181         if not other:
182             other = Coord(0, 0)
183         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
184     def bearing(self):
185         return 1.90985*math.atan2(self.j, self.i)
186     def sgn(self):
187         s = Coord()
188         if self.i == 0:
189             s.i = 0
190         elif self.i < 0:
191             s.i = -1
192         else:
193             s.i = 1
194         if self.j == 0:
195             s.j = 0
196         elif self.j < 0:
197             s.j = -1
198         else:
199             s.j = 1
200         return s
201     def quadrant(self):
202         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
203         return self.roundtogrid() // QUADSIZE
204     def sector(self):
205         return self.roundtogrid() % QUADSIZE
206     def __str__(self):
207         if self.i is None or self.j is None:
208             return "Nowhere"  # pragma: no cover
209         if (game.options & OPTION_ALPHAMERIC):
210             return letterize(self.i + 1) + str(self.j + 1)
211         return "%s - %s" % (self.i+1, self.j+1)
212     __repr__ = __str__
213
214 class Thingy():
215     "Do not anger the Space Thingy!"
216     def __init__(self):
217         self.location = Coord()
218     def at(self, q):
219         return (q.i, q.j) == (self.location.i, self.location.j)
220
221 class Planet:
222     def __init__(self):
223         self.name = None        # string-valued if inhabited
224         self.quadrant = Coord()        # quadrant located
225         self.pclass = None        # could be ""M", "N", "O", or "destroyed"
226         self.crystals = "absent"# could be "mined", "present", "absent"
227         self.known = "unknown"        # could be "unknown", "known", "shuttle_down"
228         self.inhabited = False        # is it inhabited?
229     def __str__(self):
230         return self.name
231
232 class Quadrant:
233     def __init__(self):
234         self.stars = 0
235         self.planet = None
236         self.starbase = False
237         self.klingons = 0
238         self.romulans = 0
239         self.supernova = False
240         self.charted = False
241         self.status = "secure"        # Could be "secure", "distressed", "enslaved"
242     def __str__(self):
243         return "<Quadrant: %(klingons)d>" % self.__dict__  # pragma: no cover
244     __repr__ = __str__
245
246 class Page:
247     def __init__(self):
248         self.stars = None
249         self.starbase = False
250         self.klingons = None
251     def __repr__(self):
252         return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars)  # pragma: no cover
253
254 def fill2d(size, fillfun):
255     "Fill an empty list in 2D."
256     lst = []
257     for i in range(size):
258         lst.append([])
259         for j in range(size):
260             lst[i].append(fillfun(i, j))
261     return lst
262
263 class Snapshot:
264     def __init__(self):
265         self.snap = False       # snapshot taken
266         self.crew = 0           # crew complement
267         self.nscrem = 0         # remaining super commanders
268         self.starkl = 0         # destroyed stars
269         self.basekl = 0         # destroyed bases
270         self.nromrem = 0        # Romulans remaining
271         self.nplankl = 0        # destroyed uninhabited planets
272         self.nworldkl = 0        # destroyed inhabited planets
273         self.planets = []        # Planet information
274         self.date = 0.0           # stardate
275         self.remres = 0         # remaining resources
276         self.remtime = 0        # remaining time
277         self.baseq = []         # Base quadrant coordinates
278         self.kcmdr = []         # Commander quadrant coordinates
279         self.kscmdr = Coord()        # Supercommander quadrant coordinates
280         # the galaxy
281         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
282         # the starchart
283         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
284     def traverse(self):
285         for i in range(GALSIZE):
286             for j in range(GALSIZE):
287                 yield (i, j, self.galaxy[i][j])
288
289 class Event:
290     def __init__(self):
291         self.date = None        # A real number
292         self.quadrant = None        # A coord structure
293
294 # game options
295 OPTION_ALL        = 0xffffffff
296 OPTION_TTY        = 0x00000001        # old interface
297 OPTION_CURSES     = 0x00000002        # new interface
298 OPTION_IOMODES    = 0x00000003        # cover both interfaces
299 OPTION_PLANETS    = 0x00000004        # planets and mining (> 1974)
300 OPTION_THOLIAN    = 0x00000008        # Tholians and their webs (UT 1979 version)
301 OPTION_PROBE      = 0x00000020        # deep-space probes (DECUS version, 1980)
302 OPTION_SHOWME     = 0x00000040        # bracket Enterprise in chart (ESR, 2005)
303 OPTION_RAMMING    = 0x00000080        # enemies may ram Enterprise (Almy, 1979)
304 OPTION_MVBADDY    = 0x00000100        # more enemies can move (Almy, 1979?)
305 OPTION_AUTOPASS   = 0x00000200        # Autogenerate password (Almy, 1997?)
306 OPTION_BLKHOLE    = 0x00000400        # black hole may timewarp you (Stas, 2005)
307 OPTION_BASE       = 0x00000800        # bases have good shields (Stas, 2005)
308 OPTION_WORLDS     = 0x00001000        # logic for inhabited worlds (ESR, 2006)
309 OPTION_AUTOSCAN   = 0x00002000        # automatic LRSCAN before CHART (ESR, 2006)
310 OPTION_CAPTURE    = 0x00004000        # Enable BSD-Trek capture (Almy, 2013).
311 OPTION_CLOAK      = 0x80008000        # Enable BSD-Trek capture (Almy, 2013).
312 OPTION_ALMY       = 0x01000000        # Almy's death ray upgrade (1997?)
313 OPTION_COLOR      = 0x04000000        # enable color display (ESR, 2010)
314 OPTION_DOTFILL    = 0x08000000        # fix dotfill glitch in chart (ESR, 2019)
315 OPTION_ALPHAMERIC = 0x10000000        # Alpha Y coordinates (ESR, 2023)
316
317 option_names = {
318     "ALL": OPTION_ALL,
319     "TTY": OPTION_TTY,
320     "IOMODES": OPTION_IOMODES,
321     "PLANETS": OPTION_PLANETS,
322     "PROBE": OPTION_PROBE,
323     "SHOWME": OPTION_SHOWME,
324     "RAMMING": OPTION_RAMMING,
325     "MVBADDY": OPTION_MVBADDY,
326     "AUTOPASS": OPTION_AUTOPASS,
327     "BLKHOLE": OPTION_BLKHOLE,
328     "BASE": OPTION_BASE,
329     "WORLDS": OPTION_WORLDS,
330     "AUTOSCAN": OPTION_AUTOSCAN,
331     "CAPTURE": OPTION_CAPTURE,
332     "CLOAK": OPTION_CLOAK,
333     "ALMY": OPTION_ALMY,
334     "COLOR": OPTION_COLOR,
335     "DOTFILL": OPTION_DOTFILL,
336     "ALPHAMERIC": OPTION_ALPHAMERIC,
337     }
338
339 # 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.nextstep():
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.nextstep()
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.nextstep()
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.nextstep():
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.nextstep()
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 nextstep(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.nextstep()
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 (game.options & OPTION_ALMY):
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     # Ugh. This test (For Tom Almy's death-ray upgrade) was inverted for a long time.
5084     # Furthermore, somebody (ESR or Stas?) changed Tom Almy's 0.7 upgraded chance of
5085     # working to 0.5.
5086     if game.options & OPTION_ALMY:
5087         dprob = 0.5
5088     r = rnd.real()
5089     if r > dprob:
5090         prouts(_("Sulu- \"Captain!  It's working!\""))
5091         skip(2)
5092         while len(game.enemies) > 0:
5093             deadkl(game.enemies[-1].location, game.quad[game.enemies[-1].location.i][game.enemies[-1].location.j],game.enemies[-1].location)
5094         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5095         if game.unwon() == 0:
5096             finish(FWON)
5097         if (game.options & OPTION_ALMY):
5098             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5099             if rnd.withprob(0.05):
5100                 prout(_("   is still operational.\""))
5101             else:
5102                 prout(_("   has been rendered nonfunctional.\""))
5103                 game.damage[DDRAY] = 39.95
5104         return
5105     r = rnd.real()        # Pick failure method
5106     if r <= 0.30:
5107         prouts(_("Sulu- \"Captain!  It's working!\""))
5108         skip(1)
5109         prouts(_("***RED ALERT!  RED ALERT!"))
5110         skip(1)
5111         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5112         skip(1)
5113         prouts(_("***RED ALERT!  RED A*L********************************"))
5114         skip(1)
5115         stars()
5116         prouts(_("******************   KA-BOOM!!!!   *******************"))
5117         skip(1)
5118         kaboom()
5119         return
5120     if r <= 0.55:
5121         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5122         skip(1)
5123         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5124         skip(1)
5125         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5126         prout(_("  have apparently been transformed into strange mutations."))
5127         prout(_("  Vulcans do not seem to be affected."))
5128         skip(1)
5129         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5130         finish(FDRAY)
5131         return
5132     if r <= 0.75:
5133         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5134         skip(2)
5135         proutn(_("Spock-  \"I believe the word is"))
5136         prouts(_(" *ASTONISHING*"))
5137         prout(_(" Mr. Sulu."))
5138         for i in range(QUADSIZE):
5139             for j in range(QUADSIZE):
5140                 if game.quad[i][j] == '.':
5141                     game.quad[i][j] = '?'
5142         prout(_("  Captain, our quadrant is now infested with"))
5143         prouts(_(" - - - - - -  *THINGS*."))
5144         skip(1)
5145         prout(_("  I have no logical explanation.\""))
5146         return
5147     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5148     skip(1)
5149     prout(_("Scotty-  \"There are so many tribbles down here"))
5150     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5151     finish(FTRIBBLE)
5152     return
5153
5154 # Code from reports.c begins here
5155
5156 def attackreport(curt):
5157     "eport status of bases under attack."
5158     if not curt:
5159         if is_scheduled(FCDBAS):
5160             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5161             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5162         elif game.isatb == 1:
5163             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5164             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5165         else:
5166             prout(_("No Starbase is currently under attack."))
5167     else:
5168         if is_scheduled(FCDBAS):
5169             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5170         if game.isatb:
5171             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5172         clreol()
5173
5174 def report():
5175     # report on general game status
5176     scanner.chew()
5177     # pylint: disable=consider-using-ternary
5178     s1 = (game.thawed and _("thawed ")) or ""
5179     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5180     s3 = (None, _("novice"), _("fair"),
5181           _("good"), _("expert"), _("emeritus"))[game.skill]
5182     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5183     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5184         prout(_("No plaque is allowed."))
5185     if game.tourn:
5186         prout(_("This is tournament game %d.") % game.tourn)
5187     prout(_("Your secret password is \"%s\"") % game.passwd)
5188     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5189                                                       (game.inkling + game.incom + game.inscom)))
5190     if game.incom - len(game.state.kcmdr):
5191         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5192     elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
5193         prout(_(", but no Commanders."))
5194     else:
5195         prout(".")
5196     if game.skill > SKILL_FAIR:
5197         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5198     if len(game.state.baseq) != game.inbase:
5199         proutn(_("There "))
5200         if game.inbase-len(game.state.baseq)==1:
5201             proutn(_("has been 1 base"))
5202         else:
5203             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5204         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5205     else:
5206         prout(_("There are %d bases.") % game.inbase)
5207     if communicating() or game.iseenit:
5208         # Don't report this if not seen and
5209         # either the radio is dead or not at base!
5210         attackreport(False)
5211         game.iseenit = True
5212     if game.casual:
5213         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5214     if game.brigcapacity != game.brigfree:
5215         embriggened = game.brigcapacity-game.brigfree
5216         if embriggened == 1:
5217             prout(_("1 Klingon in brig"))
5218         else:
5219             prout(_("%d Klingons in brig.") %  embriggened)
5220         if game.kcaptured == 0:
5221             pass
5222         elif game.kcaptured == 1:
5223             prout(_("1 captured Klingon turned in to Starfleet."))
5224         else:
5225             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5226     if game.nhelp:
5227         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5228     if game.ship == 'E':
5229         proutn(_("You have "))
5230         if game.nprobes:
5231             proutn("%d" % (game.nprobes))
5232         else:
5233             proutn(_("no"))
5234         proutn(_(" deep space probe"))
5235         if game.nprobes!=1:
5236             proutn(_("s"))
5237         prout(".")
5238     if communicating() and is_scheduled(FDSPROB):
5239         if game.isarmed:
5240             proutn(_("An armed deep space probe is in "))
5241         else:
5242             proutn(_("A deep space probe is in "))
5243         prout("Quadrant %s." % game.probe.quadrant())
5244     if game.icrystl:
5245         if game.cryprob <= .05:
5246             prout(_("Dilithium crystals aboard ship... not yet used."))
5247         else:
5248             i=0
5249             ai = 0.05
5250             while game.cryprob > ai:
5251                 ai *= 2.0
5252                 i += 1
5253             prout(_("Dilithium crystals have been used %d time%s.") % \
5254                   (i, (_("s"), "")[i==1]))
5255     skip(1)
5256
5257 def lrscan(silent):
5258     "Long-range sensor scan."
5259     if damaged(DLRSENS):
5260         # Now allow base's sensors if docked
5261         if game.condition != "docked":
5262             if not silent:
5263                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5264             return
5265         if not silent:
5266             prout(_("Starbase's long-range scan"))
5267     elif not silent:
5268         prout(_("Long-range scan"))
5269     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5270         if not silent:
5271             proutn(" ")
5272         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5273             if not Coord(x, y).valid_quadrant():
5274                 if not silent:
5275                     proutn("  -1")
5276             else:
5277                 if not damaged(DRADIO):
5278                     game.state.galaxy[x][y].charted = True
5279                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5280                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5281                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5282                 if not silent and game.state.galaxy[x][y].supernova:
5283                     proutn(" ***")
5284                 elif not silent:
5285                     cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
5286                     proutn(((3 - len(cn)) * '.') + cn)
5287         if not silent:
5288             prout(" ")
5289
5290 def damagereport():
5291     "Damage report."
5292     jdam = False
5293     scanner.chew()
5294     for i in range(NDEVICES):
5295         if damaged(i):
5296             if not jdam:
5297                 prout(_("DEVICE                             REPAIR TIMES"))
5298                 prout(_("                                   IN FLIGHT                DOCKED"))
5299                 jdam = True
5300             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5301                                                game.damage[i]+0.05,
5302                                                DOCKFAC*game.damage[i]+0.005))
5303     if not jdam:
5304         prout(_("All devices functional."))
5305
5306 def rechart():
5307     "Update the chart in the Enterprise's computer from galaxy data."
5308     game.lastchart = game.state.date
5309     for i in range(GALSIZE):
5310         for j in range(GALSIZE):
5311             if game.state.galaxy[i][j].charted:
5312                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5313                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5314                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5315
5316 def chart():
5317     "Display the star chart."
5318     scanner.chew()
5319     if (game.options & OPTION_AUTOSCAN):
5320         lrscan(silent=True)
5321     if communicating():
5322         rechart()
5323     if game.lastchart < game.state.date and game.condition == "docked":
5324         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5325         rechart()
5326     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5327     if game.state.date > game.lastchart:
5328         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5329     prout("      1    2    3    4    5    6    7    8")
5330     for i in range(GALSIZE):
5331         if (game.options & OPTION_ALPHAMERIC):
5332             proutn("%c |" % letterize(i+1))
5333         else:
5334             proutn("%d |" % (i+1))
5335         for j in range(GALSIZE):
5336             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5337                 proutn("<")
5338             else:
5339                 proutn(" ")
5340             if game.state.galaxy[i][j].supernova:
5341                 show = "***"
5342             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5343                 show = ".1."
5344             elif game.state.galaxy[i][j].charted:
5345                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5346                 if (game.options & OPTION_DOTFILL):
5347                     show = show.replace(" ", ".")
5348             else:
5349                 show = "..."
5350             proutn(show)
5351             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5352                 proutn(">")
5353             else:
5354                 proutn(" ")
5355         proutn("  |")
5356         if i<GALSIZE:
5357             skip(1)
5358
5359 def sectscan(goodScan, i, j):
5360     "Light up an individual dot in a sector."
5361     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5362         if game.quad[i][j] in ('E', 'F'):
5363             if game.iscloaked:
5364                 highvideo()     # pragma: no cover
5365             textcolor({"green":GREEN,
5366                        "yellow":YELLOW,
5367                        "red":RED,
5368                        "docked":CYAN,
5369                        "dead":BROWN}[game.condition])
5370         else:
5371             textcolor({'?':LIGHTMAGENTA,
5372                        'K':LIGHTRED,
5373                        'S':LIGHTRED,
5374                        'C':LIGHTRED,
5375                        'R':LIGHTRED,
5376                        'T':LIGHTRED,
5377                        '@':LIGHTGREEN,
5378                        'P':LIGHTGREEN,
5379                       }.get(game.quad[i][j], DEFAULT))
5380         proutn("%c " % game.quad[i][j])
5381         textcolor(DEFAULT)
5382     else:
5383         proutn("- ")
5384
5385 def status(req=0):
5386     "Emit status report lines"
5387     if not req or req == 1:
5388         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5389                % (game.state.date, game.state.remtime))
5390     if not req or req == 2:
5391         if game.condition != "docked":
5392             newcnd()
5393         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5394                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5395         if game.iscloaked:
5396             prout(_(", CLOAKED"))
5397     if not req or req == 3:
5398         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5399     if not req or req == 4:
5400         if damaged(DLIFSUP):
5401             if game.condition == "docked":
5402                 s = _("DAMAGED, Base provides")
5403             else:
5404                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5405         else:
5406             s = _("ACTIVE")
5407         prstat(_("Life Support"), s)
5408     if not req or req == 5:
5409         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5410     if not req or req == 6:
5411         extra = ""
5412         if game.icrystl and (game.options & OPTION_SHOWME):
5413             extra = _(" (have crystals)")
5414         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5415     if not req or req == 7:
5416         prstat(_("Torpedoes"), "%d" % (game.torps))
5417     if not req or req == 8:
5418         if damaged(DSHIELD):
5419             s = _("DAMAGED,")
5420         elif game.shldup:
5421             s = _("UP,")
5422         else:
5423             s = _("DOWN,")
5424         data = _(" %d%% %.1f units") \
5425                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5426         prstat(_("Shields"), s+data)
5427     if not req or req == 9:
5428         prstat(_("Klingons Left"), "%d" % game.unwon())
5429     if not req or req == 10:
5430         if game.options & OPTION_WORLDS:
5431             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5432             if plnet and plnet.inhabited:
5433                 prstat(_("Major system"), plnet.name)
5434             else:
5435                 prout(_("Sector is uninhabited"))
5436     elif not req or req == 11:
5437         attackreport(not req)
5438
5439 def request():
5440     "Request specified status data, a historical relic from slow TTYs."
5441     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5442     while scanner.nexttok() == "IHEOL":
5443         proutn(_("Information desired? "))
5444     scanner.chew()
5445     if scanner.token in requests:
5446         status(requests.index(scanner.token))
5447     else:
5448         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5449         prout(("  date, condition, position, lsupport, warpfactor,"))
5450         prout(("  energy, torpedoes, shields, klingons, system, time."))
5451
5452 def srscan():
5453     "Short-range scan."
5454     goodScan=True
5455     if damaged(DSRSENS):
5456         # Allow base's sensors if docked
5457         if game.condition != "docked":
5458             prout(_("   S.R. SENSORS DAMAGED!"))
5459             goodScan=False
5460         else:
5461             prout(_("  [Using Base's sensors]"))
5462     else:
5463         prout(_("     Short-range scan"))
5464     if goodScan and communicating():
5465         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5466         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5467         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5468         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5469     prout("    1 2 3 4 5 6 7 8 9 10")
5470     if game.condition != "docked":
5471         newcnd()
5472     for i in range(QUADSIZE):
5473         if (game.options & OPTION_ALPHAMERIC):
5474             proutn("%c   " % letterize(i+1))
5475         else:
5476             proutn("%2d  " % (i+1))
5477         for j in range(QUADSIZE):
5478             sectscan(goodScan, i, j)
5479         skip(1)
5480
5481 def eta():
5482     "Use computer to get estimated time of arrival for a warp jump."
5483     w1 = Coord(); w2 = Coord()
5484     prompt = False
5485     if damaged(DCOMPTR):
5486         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5487         skip(1)
5488         return
5489     if scanner.nexttok() != "IHREAL":
5490         prompt = True
5491         scanner.chew()
5492         proutn(_("Destination quadrant and/or sector? "))
5493         if scanner.nexttok()!="IHREAL":
5494             huh()
5495             return
5496     w1.j = int(scanner.real-0.5)
5497     if scanner.nexttok() != "IHREAL":
5498         huh()
5499         return
5500     w1.i = int(scanner.real-0.5)
5501     if scanner.nexttok() == "IHREAL":
5502         w2.j = int(scanner.real-0.5)
5503         if scanner.nexttok() != "IHREAL":
5504             huh()
5505             return
5506         w2.i = int(scanner.real-0.5)
5507     else:
5508         if game.quadrant.j>w1.i:
5509             w2.i = 0
5510         else:
5511             w2.i=QUADSIZE-1
5512         if game.quadrant.i>w1.j:
5513             w2.j = 0
5514         else:
5515             w2.j=QUADSIZE-1
5516     if not w1.valid_quadrant() or not w2.valid_sector():
5517         huh()
5518         return
5519     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5520                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5521     wfl = False
5522     if prompt:
5523         prout(_("Answer \"no\" if you don't know the value:"))
5524     while True:
5525         scanner.chew()
5526         proutn(_("Time or arrival date? "))
5527         if scanner.nexttok()=="IHREAL":
5528             ttime = scanner.real
5529             if ttime > game.state.date:
5530                 ttime -= game.state.date # Actually a star date
5531             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5532             if ttime <= 1e-10 or twarp > 10:
5533                 prout(_("We'll never make it, sir."))
5534                 scanner.chew()
5535                 return
5536             twarp = max(twarp, 1.0)
5537             break
5538         scanner.chew()
5539         proutn(_("Warp factor? "))
5540         if scanner.nexttok()== "IHREAL":
5541             wfl = True
5542             twarp = scanner.real
5543             if twarp<1.0 or twarp > 10.0:
5544                 huh()
5545                 return
5546             break
5547         prout(_("Captain, certainly you can give me one of these."))
5548     while True:
5549         scanner.chew()
5550         ttime = (10.0*dist)/twarp**2
5551         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5552         if tpower >= game.energy:
5553             prout(_("Insufficient energy, sir."))
5554             if not game.shldup or tpower > game.energy*2.0:
5555                 if not wfl:
5556                     return
5557                 proutn(_("New warp factor to try? "))
5558                 if scanner.nexttok() == "IHREAL":
5559                     wfl = True
5560                     twarp = scanner.real
5561                     if twarp<1.0 or twarp > 10.0:
5562                         huh()
5563                         return
5564                     continue
5565                 else:
5566                     scanner.chew()
5567                     skip(1)
5568                     return
5569             prout(_("But if you lower your shields,"))
5570             proutn(_("remaining"))
5571             tpower /= 2
5572         else:
5573             proutn(_("Remaining"))
5574         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5575         if wfl:
5576             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5577         elif twarp==1.0:
5578             prout(_("Any warp speed is adequate."))
5579         else:
5580             prout(_("Minimum warp needed is %.2f,") % (twarp))
5581             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5582         if game.state.remtime < ttime:
5583             prout(_("Unfortunately, the Federation will be destroyed by then."))
5584         if twarp > 6.0:
5585             prout(_("You'll be taking risks at that speed, Captain"))
5586         if (game.isatb==1 and game.state.kscmdr == w1 and \
5587              scheduled(FSCDBAS)< ttime+game.state.date) or \
5588             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5589             prout(_("The starbase there will be destroyed by then."))
5590         proutn(_("New warp factor to try? "))
5591         if scanner.nexttok() == "IHREAL":
5592             wfl = True
5593             twarp = scanner.real
5594             if twarp<1.0 or twarp > 10.0:
5595                 huh()
5596                 return
5597         else:
5598             scanner.chew()
5599             skip(1)
5600             return
5601
5602 # This is new in SST2K.
5603
5604 def goptions():
5605     mode = scanner.nexttok()
5606     if mode == "IHEOL":
5607         active = []
5608         for k, v in option_names.items():
5609             if (v & game.options) and k != "ALL":
5610                 active.append(k)
5611         active.sort()
5612         prout(str(" ".join(active)))
5613     elif scanner.token in {"set", "clear"}:
5614         mode = scanner.token
5615         changemask = 0
5616         while True:
5617             scanner.nexttok()
5618             if scanner.type == "IHEOL":
5619                 break
5620             if scanner.token.upper() in option_names:
5621                 changemask |= option_names[scanner.token.upper()]
5622             else:
5623                 prout(_("No such option as ") + scanner.token)
5624         if mode == "set":
5625             if (not (game.options & OPTION_CURSES)) and (changemask & OPTION_CURSES):   # pragma: no cover
5626                 iostart()
5627             game.options |= changemask
5628         elif mode == "clear":
5629             if (game.options & OPTION_CURSES) and (not (changemask & OPTION_CURSES)):   # pragma: no cover
5630                 ioend()
5631             game.options &=~ changemask
5632         prout(_("Acknowledged, Captain."))
5633     else:
5634         huh()
5635     scanner.chew()
5636     skip(1)
5637
5638 # Code from setup.c begins here
5639
5640 def prelim():
5641     "Issue a historically correct banner."
5642     skip(2)
5643     prout(_("-SUPER- STAR TREK"))
5644     skip(1)
5645 # From the FORTRAN original
5646 #    prout(_("Latest update-21 Sept 78"))
5647 #    skip(1)
5648
5649 def freeze(boss):
5650     "Save game."
5651     if boss:
5652         scanner.push("emsave.trk")
5653     key = scanner.nexttok()
5654     if key == "IHEOL":
5655         proutn(_("File name: "))
5656         key = scanner.nexttok()
5657     if key != "IHALPHA":
5658         huh()
5659         return
5660     if '.' not in scanner.token:
5661         scanner.token += ".trk"
5662     try:
5663         fp = open(scanner.token, "wb")
5664     except IOError:
5665         prout(_("Can't freeze game as file %s") % scanner.token)
5666         return
5667     pickle.dump(game, fp)
5668     fp.close()
5669     scanner.chew()
5670
5671 def thaw():
5672     "Retrieve saved game."
5673     global game
5674     game.passwd = None
5675     key = scanner.nexttok()
5676     if key == "IHEOL":
5677         proutn(_("File name: "))
5678         key = scanner.nexttok()
5679     if key != "IHALPHA":
5680         huh()
5681         return True
5682     if '.' not in scanner.token:
5683         scanner.token += ".trk"
5684     try:
5685         fp = open(scanner.token, "rb")
5686     except IOError:
5687         prout(_("Can't thaw game in %s") % scanner.token)
5688         return True
5689     game = pickle.load(fp)
5690     fp.close()
5691     scanner.chew()
5692     return False
5693
5694 # I used <http://www.memory-alpha.org> to find planets
5695 # with references in ST:TOS.  Earth and the Alpha Centauri
5696 # Colony have been omitted.
5697 #
5698 # Some planets marked Class G and P here will be displayed as class M
5699 # because of the way planets are generated. This is a known bug.
5700 systnames = (
5701     # Federation Worlds
5702     _("Andoria (Fesoan)"),        # several episodes
5703     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5704     _("Vulcan (T'Khasi)"),        # many episodes
5705     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5706     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5707     _("Ardana"),                # TOS: "The Cloud Minders"
5708     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5709     _("Gideon"),                # TOS: "The Mark of Gideon"
5710     _("Aldebaran III"),                # TOS: "The Deadly Years"
5711     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5712     _("Altair IV"),                # TOS: "Amok Time
5713     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5714     _("Benecia"),                # TOS: "The Conscience of the King"
5715     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5716     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5717     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5718     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5719     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5720     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5721     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5722     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5723     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5724     _("Janus IV"),                # TOS: "The Devil in the Dark"
5725     _("Makus III"),                # TOS: "The Galileo Seven"
5726     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5727     _("Omega IV"),                # TOS: "The Omega Glory"
5728     _("Regulus V"),                # TOS: "Amok Time
5729     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5730     # Worlds from BSD Trek
5731     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5732     _("Beta III"),                # TOS: "The Return of the Archons"
5733     _("Triacus"),                # TOS: "And the Children Shall Lead",
5734     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5735     #        # Others
5736     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5737     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5738     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5739     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5740     #    _("Tiburon"),                # TOS: "The Way to Eden"
5741     #    _("Merak II"),                # TOS: "The Cloud Minders"
5742     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5743     #    _("Iotia"),                # TOS: "A Piece of the Action"
5744 )
5745
5746 device = (
5747     _("S. R. Sensors"), \
5748     _("L. R. Sensors"), \
5749     _("Phasers"), \
5750     _("Photon Tubes"), \
5751     _("Life Support"), \
5752     _("Warp Engines"), \
5753     _("Impulse Engines"), \
5754     _("Shields"), \
5755     _("Subspace Radio"), \
5756     _("Shuttle Craft"), \
5757     _("Computer"), \
5758     _("Navigation System"), \
5759     _("Transporter"), \
5760     _("Shield Control"), \
5761     _("Death Ray"), \
5762     _("D. S. Probe"), \
5763     _("Cloaking Device"), \
5764 )
5765
5766 def setup():
5767     "Prepare to play, set up cosmos."
5768     w = Coord()
5769     #  Decide how many of everything
5770     if choose():
5771         return # frozen game
5772     # Prepare the Enterprise
5773     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5774     game.ship = 'E'
5775     game.state.crew = FULLCREW
5776     game.energy = game.inenrg = 5000.0
5777     game.shield = game.inshld = 2500.0
5778     game.inlsr = 4.0
5779     game.lsupres = 4.0
5780     game.quadrant = randplace(GALSIZE)
5781     game.sector = randplace(QUADSIZE)
5782     game.torps = game.intorps = 10
5783     game.nprobes = rnd.integer(2, 5)
5784     game.warpfac = 5.0
5785     for i in range(NDEVICES):
5786         game.damage[i] = 0.0
5787     # Set up assorted game parameters
5788     game.battle = Coord()
5789     game.state.date = game.indate = 100.0 * rnd.real(20, 51)
5790     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5791     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5792     game.isatb = game.state.nplankl = 0
5793     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5794     game.iscraft = "onship"
5795     game.landed = False
5796     game.alive = True
5797
5798     # the galaxy
5799     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5800     # the starchart
5801     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5802
5803     game.state.planets = []      # Planet information
5804     game.state.baseq = []      # Base quadrant coordinates
5805     game.state.kcmdr = []      # Commander quadrant coordinates
5806     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5807
5808     # Starchart is functional but we've never seen it
5809     game.lastchart = FOREVER
5810     # Put stars in the galaxy
5811     game.instar = 0
5812     for i in range(GALSIZE):
5813         for j in range(GALSIZE):
5814             # Can't have more stars per quadrant than fit in one decimal digit,
5815             # if we do the chart representation will break.
5816             k = rnd.integer(1, min(10, QUADSIZE**2/10))
5817             game.instar += k
5818             game.state.galaxy[i][j].stars = k
5819     # Locate star bases in galaxy
5820     if game.idebug:
5821         prout("=== Allocating %d bases" % game.inbase)                # pragma: no cover
5822     for i in range(game.inbase):
5823         while True:
5824             while True:
5825                 w = randplace(GALSIZE)
5826                 if not game.state.galaxy[w.i][w.j].starbase:
5827                     break
5828             contflag = False
5829             # C version: for (j = i-1; j > 0; j--)
5830             # so it did them in the opposite order.
5831             for j in range(1, i):
5832                 # Improved placement algorithm to spread out bases
5833                 distq = (w - game.state.baseq[j]).distance()
5834                 if distq < 6.0*(BASEMAX+1-game.inbase) and rnd.withprob(0.75):
5835                     contflag = True
5836                     if game.idebug:
5837                         prout("=== Abandoning base #%d at %s" % (i, w))                # pragma: no cover
5838                     break
5839                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5840                     if game.idebug:
5841                         prout("=== Saving base #%d, close to #%d" % (i, j))                # pragma: no cover
5842             if not contflag:
5843                 break
5844         if game.idebug:
5845             prout("=== Placing base #%d in quadrant %s" % (i, w))                # pragma: no cover
5846         game.state.baseq.append(w)
5847         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5848     # Position ordinary Klingon Battle Cruisers
5849     krem = game.inkling
5850     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5851     klumper = min(klumper, MAXKLQUAD)
5852     while True:
5853         r = rnd.real()
5854         klump = int((1.0 - r*r)*klumper)
5855         klump = min(klump, krem)
5856         krem -= klump
5857         while True:
5858             w = randplace(GALSIZE)
5859             if not game.state.galaxy[w.i][w.j].supernova and \
5860                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5861                 break
5862         game.state.galaxy[w.i][w.j].klingons += klump
5863         if krem <= 0:
5864             break
5865     # Position Klingon Commander Ships
5866     for i in range(game.incom):
5867         while True:
5868             w = randplace(GALSIZE)
5869             if not welcoming(w) or w in game.state.kcmdr:
5870                 continue
5871             if (game.state.galaxy[w.i][w.j].klingons or rnd.withprob(0.25)):
5872                 break
5873         game.state.galaxy[w.i][w.j].klingons += 1
5874         game.state.kcmdr.append(w)
5875     # Locate planets in galaxy
5876     for i in range(game.inplan):
5877         while True:
5878             w = randplace(GALSIZE)
5879             if game.state.galaxy[w.i][w.j].planet is None:
5880                 break
5881         new = Planet()
5882         new.quadrant = w
5883         new.crystals = "absent"
5884         if (game.options & OPTION_WORLDS) and i < NINHAB:
5885             new.pclass = "M"        # All inhabited planets are class M
5886             new.crystals = "absent"
5887             new.known = "known"
5888             new.name = systnames[i]
5889             new.inhabited = True
5890         else:
5891             new.pclass = ("M", "N", "O")[rnd.integer(0, 3)]
5892             if rnd.withprob(0.33):
5893                 new.crystals = "present"
5894             new.known = "unknown"
5895             new.inhabited = False
5896         game.state.galaxy[w.i][w.j].planet = new
5897         game.state.planets.append(new)
5898     # Locate Romulans
5899     for i in range(game.state.nromrem):
5900         w = randplace(GALSIZE)
5901         game.state.galaxy[w.i][w.j].romulans += 1
5902     # Place the Super-Commander if needed
5903     if game.state.nscrem > 0:
5904         while True:
5905             w = randplace(GALSIZE)
5906             if welcoming(w):
5907                 break
5908         game.state.kscmdr = w
5909         game.state.galaxy[w.i][w.j].klingons += 1
5910     # Initialize times for extraneous events
5911     schedule(FSNOVA, expran(0.5 * game.intime))
5912     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5913     schedule(FSNAP, rnd.real(1.0, 2.0)) # Force an early snapshot
5914     schedule(FBATTAK, expran(0.3*game.intime))
5915     unschedule(FCDBAS)
5916     if game.state.nscrem:
5917         schedule(FSCMOVE, 0.2777)
5918     else:
5919         unschedule(FSCMOVE)
5920     unschedule(FSCDBAS)
5921     unschedule(FDSPROB)
5922     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5923         schedule(FDISTR, expran(1.0 + game.intime))
5924     else:
5925         unschedule(FDISTR)
5926     unschedule(FENSLV)
5927     unschedule(FREPRO)
5928     # Place thing (in tournament game, we don't want one!)
5929     # New in SST2K: never place the Thing near a starbase.
5930     # This makes sense and avoids a special case in the old code.
5931     if game.tourn is None:
5932         # Avoid distrubing the RNG chain.  This code
5933         # was a late fix and we don't want to mess up
5934         # all the regression tests.
5935         state = randomizer.getrngstate()
5936         while True:
5937             thing.location = randplace(GALSIZE)
5938             # Put it somewhere a starbase is not
5939             if thing.location not in game.state.baseq:
5940                 break
5941         randomizer.setrngstate(state)
5942     skip(2)
5943     game.state.snap = False
5944     if game.skill == SKILL_NOVICE:
5945         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5946         prout(_("a deadly Klingon invasion force. As captain of the United"))
5947         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5948         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5949         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5950         prout(_("your mission.  As you proceed you may be given more time."))
5951         skip(1)
5952         prout(_("You will have %d supporting starbases.") % (game.inbase))
5953         proutn(_("Starbase locations-  "))
5954     else:
5955         prout(_("Stardate %d.") % int(game.state.date))
5956         skip(1)
5957         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5958         prout(_("An unknown number of Romulans."))
5959         if game.state.nscrem:
5960             prout(_("And one (GULP) Super-Commander."))
5961         prout(_("%d stardates.") % int(game.intime))
5962         proutn(_("%d starbases in ") % game.inbase)
5963     for i in range(game.inbase):
5964         proutn(repr(game.state.baseq[i]))
5965         proutn("  ")
5966     skip(2)
5967     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5968     proutn(_(" Sector %s") % game.sector)
5969     skip(2)
5970     prout(_("Good Luck!"))
5971     if game.state.nscrem:
5972         prout(_("  YOU'LL NEED IT."))
5973     waitfor()
5974     clrscr()
5975     setwnd(message_window)
5976     newqad()
5977     if len(game.enemies) - (thing.location == game.quadrant) - (game.tholian is not None):
5978         game.shldup = True
5979     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5980         attack(torps_ok=False)
5981
5982 def choose():
5983     "Choose your game type."
5984     while True:
5985         game.tourn = None
5986         game.length = 0
5987         game.thawed = False
5988         game.skill = SKILL_NONE
5989         # Do not chew here, we want to use command-line tokens
5990         if not scanner.inqueue: # Can start with command line options
5991             proutn(_("Would you like a regular, tournament, or saved game? "))
5992         scanner.nexttok()
5993         if scanner.sees("tournament"):
5994             game.tourn = 0
5995             while scanner.nexttok() == "IHEOL":
5996                 proutn(_("Type in tournament number-"))
5997             if scanner.real == 0:
5998                 scanner.chew()
5999                 continue # We don't want a blank entry
6000             game.tourn = int(round(scanner.real))
6001             rnd.seed(scanner.real)
6002             if logfp:
6003                 logfp.write("# rnd.seed(%d)\n" % scanner.real)
6004             break
6005         if scanner.sees("saved") or scanner.sees("frozen"):
6006             if thaw():
6007                 continue
6008             scanner.chew()
6009             if game.passwd is None:
6010                 continue
6011             if not game.alldone:
6012                 game.thawed = True # No plaque if not finished
6013             report()
6014             waitfor()
6015             return True
6016         if scanner.sees("regular"):
6017             break
6018         proutn(_("What game type is \"%s\"? ") % scanner.token)
6019         scanner.chew()
6020     while game.length==0 or game.skill==SKILL_NONE:
6021         if scanner.nexttok() == "IHALPHA":
6022             if scanner.sees("short"):
6023                 game.length = 1
6024             elif scanner.sees("medium"):
6025                 game.length = 2
6026             elif scanner.sees("long"):
6027                 game.length = 4
6028             elif scanner.sees("novice"):
6029                 game.skill = SKILL_NOVICE
6030             elif scanner.sees("fair"):
6031                 game.skill = SKILL_FAIR
6032             elif scanner.sees("good"):
6033                 game.skill = SKILL_GOOD
6034             elif scanner.sees("expert"):
6035                 game.skill = SKILL_EXPERT
6036             elif scanner.sees("emeritus"):
6037                 game.skill = SKILL_EMERITUS
6038             else:
6039                 proutn(_("What skill level is is \""))
6040                 proutn(scanner.token)
6041                 prout("\"?")
6042         else:
6043             scanner.chew()
6044             if game.length==0:
6045                 proutn(_("Would you like a Short, Medium, or Long game? "))
6046             elif game.skill == SKILL_NONE:
6047                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
6048     # Choose game options -- added by ESR for SST2K
6049     if scanner.nexttok() != "IHALPHA":
6050         scanner.chew()
6051         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
6052         scanner.nexttok()
6053     if scanner.sees("plain"):
6054         # Approximates the UT FORTRAN version.
6055         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_CAPTURE | OPTION_CLOAK | OPTION_ALMY | OPTION_AUTOPASS | OPTION_DOTFILL | OPTION_ALPHAMERIC)
6056     elif scanner.sees("almy"):
6057         # Approximates Tom Almy's version.
6058         game.options &=~ (OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_DOTFILL | OPTION_ALPHAMERIC)
6059     elif scanner.sees("fancy") or scanner.sees("\n"):
6060         pass
6061     elif len(scanner.token):
6062         proutn(_("What game style is \"%s\"?") % scanner.token)
6063     setpassword()
6064     if game.passwd == "debug":                # pragma: no cover
6065         game.idebug = True
6066         prout("=== Debug mode enabled.")
6067     # Use parameters to generate initial values of things
6068     game.damfac = 0.5 * game.skill
6069     game.inbase = rnd.integer(BASEMIN, BASEMAX+1)
6070     game.inplan = 0
6071     if game.options & OPTION_PLANETS:
6072         game.inplan += rnd.integer(MAXUNINHAB/2, MAXUNINHAB+1)
6073     if game.options & OPTION_WORLDS:
6074         game.inplan += int(NINHAB)
6075     game.state.nromrem = game.inrom = rnd.integer(2 * game.skill)
6076     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
6077     game.state.remtime = 7.0 * game.length
6078     game.intime = game.state.remtime
6079     game.inkling = int(2.0*game.intime*((game.skill+1 - 2*rnd.real())*game.skill*0.1+.15))
6080     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*rnd.real()))
6081     game.state.remres = (game.inkling+4*game.incom)*game.intime
6082     game.inresor = game.state.remres
6083     if game.inkling > 50:
6084         game.inbase += 1
6085     return False
6086
6087 def dropin(iquad=None):
6088     "Drop a feature on a random dot in the current quadrant."
6089     while True:
6090         w = randplace(QUADSIZE)
6091         if game.quad[w.i][w.j] == '.':
6092             break
6093     if iquad is not None:
6094         game.quad[w.i][w.j] = iquad
6095     return w
6096
6097 def newcnd():
6098     "Update our alert status."
6099     game.condition = "green"
6100     if game.energy < 1000.0:
6101         game.condition = "yellow"
6102     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
6103         game.condition = "red"
6104     if not game.alive:
6105         game.condition="dead"
6106
6107 def newkling():
6108     "Drop new Klingon into current quadrant."
6109     return Enemy('K', loc=dropin(), power=rnd.real(300,450)+25.0*game.skill)
6110
6111 def sortenemies():
6112     "Sort enemies by distance so 'nearest' is meaningful."
6113     game.enemies.sort(key=lambda x: x.kdist)
6114
6115 def newqad():
6116     "Set up a new state of quadrant, for when we enter or re-enter it."
6117     game.justin = True
6118     game.iplnet = None
6119     game.neutz = game.inorbit = game.landed = False
6120     game.ientesc = game.iseenit = game.isviolreported = False
6121     game.tholian = None
6122     # Create a blank quadrant
6123     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6124     if game.iscate:
6125         # Attempt to escape Super-commander, so tbeam back!
6126         game.iscate = False
6127         game.ientesc = True
6128     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6129     # cope with supernova
6130     if q.supernova:
6131         return
6132     game.klhere = q.klingons
6133     game.irhere = q.romulans
6134     # Position Starship
6135     game.quad[game.sector.i][game.sector.j] = game.ship
6136     game.enemies = []
6137     if q.klingons:
6138         # Position ordinary Klingons
6139         for _i in range(game.klhere):
6140             newkling()
6141         # If we need a commander, promote a Klingon
6142         for cmdr in game.state.kcmdr:
6143             if cmdr == game.quadrant:
6144                 e = game.enemies[game.klhere-1]
6145                 game.quad[e.location.i][e.location.j] = 'C'
6146                 e.power = rnd.real(950,1350) + 50.0*game.skill
6147                 break
6148         # If we need a super-commander, promote a Klingon
6149         if game.quadrant == game.state.kscmdr:
6150             e = game.enemies[0]
6151             game.quad[e.location.i][e.location.j] = 'S'
6152             e.power = rnd.real(1175.0,  1575.0) + 125.0*game.skill
6153             game.iscate = (game.remkl() > 1)
6154     # Put in Romulans if needed
6155     for _i in range(q.romulans):
6156         Enemy('R', loc=dropin(), power=rnd.real(400.0,850.0)+50.0*game.skill)
6157     # If quadrant needs a starbase, put it in
6158     if q.starbase:
6159         game.base = dropin('B')
6160     # If quadrant needs a planet, put it in
6161     if q.planet:
6162         game.iplnet = q.planet
6163         if not q.planet.inhabited:
6164             game.plnet = dropin('P')
6165         else:
6166             game.plnet = dropin('@')
6167     # Check for condition
6168     newcnd()
6169     # Check for RNZ
6170     if game.irhere > 0 and game.klhere == 0:
6171         game.neutz = True
6172         if not damaged(DRADIO):
6173             skip(1)
6174             prout(_("LT. Uhura- \"Captain, an urgent message."))
6175             prout(_("  I'll put it on audio.\"  CLICK"))
6176             skip(1)
6177             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6178             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6179     # Put in THING if needed
6180     if thing.location == game.quadrant:
6181         Enemy(etype='?', loc=dropin(),
6182               power=rnd.real(6000,6500.0)+250.0*game.skill)
6183         if not damaged(DSRSENS):
6184             skip(1)
6185             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6186             prout(_("    Please examine your short-range scan.\""))
6187     # Decide if quadrant needs a Tholian; lighten up if skill is low
6188     if game.options & OPTION_THOLIAN:
6189         if (game.skill < SKILL_GOOD and rnd.withprob(0.02)) or \
6190             (game.skill == SKILL_GOOD and rnd.withprob(0.05)) or \
6191             (game.skill > SKILL_GOOD and rnd.withprob(0.08)):
6192             w = Coord()
6193             while True:
6194                 w.i = rnd.withprob(0.5) * (QUADSIZE-1)
6195                 w.j = rnd.withprob(0.5) * (QUADSIZE-1)
6196                 if game.quad[w.i][w.j] == '.':
6197                     break
6198             game.tholian = Enemy(etype='T', loc=w,
6199                                  power=rnd.integer(100, 500) + 25.0*game.skill)
6200             # Reserve unoccupied corners
6201             if game.quad[0][0]=='.':
6202                 game.quad[0][0] = 'X'
6203             if game.quad[0][QUADSIZE-1]=='.':
6204                 game.quad[0][QUADSIZE-1] = 'X'
6205             if game.quad[QUADSIZE-1][0]=='.':
6206                 game.quad[QUADSIZE-1][0] = 'X'
6207             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6208                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6209     sortenemies()
6210     # And finally the stars
6211     for _i in range(q.stars):
6212         dropin('*')
6213     # Put in a few black holes
6214     for _i in range(1, 3+1):
6215         if rnd.withprob(0.5):
6216             dropin(' ')
6217     # Take out X's in corners if Tholian present
6218     if game.tholian:
6219         if game.quad[0][0]=='X':
6220             game.quad[0][0] = '.'
6221         if game.quad[0][QUADSIZE-1]=='X':
6222             game.quad[0][QUADSIZE-1] = '.'
6223         if game.quad[QUADSIZE-1][0]=='X':
6224             game.quad[QUADSIZE-1][0] = '.'
6225         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6226             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6227     # This should guarantee that replay games don't lose info about the chart
6228     if (game.options & OPTION_AUTOSCAN) or replayfp:
6229         lrscan(silent=True)
6230
6231 def setpassword():
6232     "Set the self-destruct password."
6233     if game.options & OPTION_AUTOPASS:
6234         game.passwd = ""
6235         game.passwd += chr(ord('a')+rnd.integer(26))
6236         game.passwd += chr(ord('a')+rnd.integer(26))
6237         game.passwd += chr(ord('a')+rnd.integer(26))
6238     else:
6239         while True:
6240             scanner.chew()
6241             proutn(_("Please type in a secret password- "))
6242             scanner.nexttok()
6243             game.passwd = scanner.token
6244             #game.passwd = getpass.getpass("Please type in a secret password- ")
6245             if game.passwd is not None:
6246                 break
6247
6248 # Code from sst.c begins here
6249
6250 commands = [
6251     ("SRSCAN",           OPTION_TTY),
6252     ("STATUS",           OPTION_TTY),
6253     ("REQUEST",          OPTION_TTY),
6254     ("LRSCAN",           OPTION_TTY),
6255     ("PHASERS",          0),
6256     ("TORPEDO",          0),
6257     ("PHOTONS",          0),
6258     ("MOVE",             0),
6259     ("SHIELDS",          0),
6260     ("DOCK",             0),
6261     ("DAMAGES",          0),
6262     ("CHART",            0),
6263     ("IMPULSE",          0),
6264     ("REST",             0),
6265     ("WARP",             0),
6266     ("SENSORS",          OPTION_PLANETS),
6267     ("ORBIT",            OPTION_PLANETS),
6268     ("TRANSPORT",        OPTION_PLANETS),
6269     ("MINE",             OPTION_PLANETS),
6270     ("CRYSTALS",         OPTION_PLANETS),
6271     ("SHUTTLE",          OPTION_PLANETS),
6272     ("PLANETS",          OPTION_PLANETS),
6273     ("REPORT",           0),
6274     ("COMPUTER",         0),
6275     ("COMMANDS",         0),
6276     ("EMEXIT",           0),
6277     ("PROBE",            OPTION_PROBE),
6278     ("SAVE",             0),
6279     ("FREEZE",           0),        # Synonym for SAVE
6280     ("OPTIONS",          0),
6281     ("ABANDON",          0),
6282     # No abbreviations accepted after this point
6283     ("DESTRUCT",         0),
6284     ("DEATHRAY",         0),
6285     ("CAPTURE",          OPTION_CAPTURE),
6286     ("CLOAK",            OPTION_CLOAK),
6287     ("DEBUG",            0),
6288     ("MAYDAY",           0),
6289     ("SOS",              0),        # Synonym for MAYDAY
6290     ("CALL",             0),        # Synonym for MAYDAY
6291     ("QUIT",             0),
6292     ("HELP",             0),
6293     ("SCORE",            0),
6294     ("CURSES",           0),
6295     ("",                 0),
6296 ]
6297
6298 def listCommands():                # pragma: no cover
6299     "Generate a list of legal commands."
6300     # Coverage-disabled because testing for this is fragile
6301     # in the presence of changes in the command set.
6302     prout(_("LEGAL COMMANDS ARE:"))
6303     emitted = 0
6304     for (key, opt) in commands:
6305         if not opt or (opt & game.options):
6306             proutn("%-12s " % key)
6307             emitted += 1
6308             if emitted % 5 == 4:
6309                 skip(1)
6310     skip(1)
6311
6312 def helpme():
6313     "Browse on-line help."
6314     key = scanner.nexttok()
6315     while True:
6316         if key == "IHEOL":
6317             setwnd(prompt_window)
6318             proutn(_("Help on what command? "))
6319             key = scanner.nexttok()
6320         setwnd(message_window)
6321         if key == "IHEOL":
6322             return
6323         cmds = [x[0] for x in commands]
6324         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6325             break
6326         skip(1)
6327         listCommands()
6328         key = "IHEOL"
6329         scanner.chew()
6330         skip(1)
6331     cmd = scanner.token.upper()
6332     for directory in docpath:
6333         try:
6334             fp = open(os.path.join(directory, "sst.doc"), "r")
6335             break
6336         except IOError:
6337             pass
6338     else:
6339         prout(_("Spock-  \"Captain, that information is missing from the"))
6340         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6341         proutn(_("   in these directories: %s") % ":".join(docpath))
6342         prout(".\"")
6343         # This used to continue: "You need to find SST.DOC and put
6344         # it in the current directory."
6345         return
6346     while True:
6347         linebuf = fp.readline()
6348         if linebuf == '':
6349             prout(_("Spock- \"Captain, there is no information on that command.\""))
6350             fp.close()
6351             return
6352         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6353             linebuf = linebuf[3:].strip()
6354             if cmd.upper() == linebuf:
6355                 break
6356     skip(1)
6357     prout(_("Spock- \"Captain, I've found the following information:\""))
6358     skip(1)
6359     while True:
6360         linebuf = fp.readline()
6361         if "******" in linebuf:
6362             break
6363         proutn(linebuf)
6364     fp.close()
6365
6366 def makemoves():
6367     "Command-interpretation loop."
6368     def checkviol():
6369         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6370             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6371             game.ncviol += 1
6372             game.isviolreported = True
6373     while True:         # command loop
6374         drawmaps(1)
6375         while True:        # get a command
6376             hitme = False
6377             game.optime = game.justin = False
6378             scanner.chew()
6379             setwnd(prompt_window)
6380             clrscr()
6381             proutn("COMMAND> ")
6382             if scanner.nexttok() == "IHEOL":
6383                 if game.options & OPTION_CURSES:        # pragma: no cover
6384                     makechart()
6385                 continue
6386             elif scanner.token == "":
6387                 continue
6388             game.ididit = False
6389             clrscr()
6390             setwnd(message_window)
6391             clrscr()
6392             abandon_passed = False
6393             cmd = ""    # Force cmd to persist after loop
6394             opt = 0     # Force opt to persist after loop
6395             for (cmd, opt) in commands:
6396                 # commands after ABANDON cannot be abbreviated
6397                 if cmd == "ABANDON":
6398                     abandon_passed = True
6399                 if cmd == scanner.token.upper() or (not abandon_passed \
6400                         and cmd.startswith(scanner.token.upper())):
6401                     break
6402             if cmd == "":                # pragma: no cover
6403                 listCommands()
6404                 continue
6405             elif opt and not (opt & game.options):
6406                 huh()
6407             else:
6408                 break
6409         if game.options & OPTION_CURSES:        # pragma: no cover
6410             prout("COMMAND> %s" % cmd)
6411         if cmd == "SRSCAN":                # srscan
6412             srscan()
6413         elif cmd == "STATUS":                # status
6414             status()
6415         elif cmd == "REQUEST":                # status request
6416             request()
6417         elif cmd == "LRSCAN":                # long range scan
6418             lrscan(silent=False)
6419         elif cmd == "PHASERS":                # phasers
6420             phasers()
6421             if game.ididit:
6422                 checkviol()
6423                 hitme = True
6424         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6425             torps()
6426             if game.ididit:
6427                 checkviol()
6428                 hitme = True
6429         elif cmd == "MOVE":                # move under warp
6430             warp(wcourse=None, involuntary=False)
6431         elif cmd == "SHIELDS":                # shields
6432             doshield(shraise=False)
6433             if game.ididit:
6434                 hitme = True
6435                 game.shldchg = False
6436         elif cmd == "DOCK":                # dock at starbase
6437             dock(True)
6438             if game.ididit:
6439                 attack(torps_ok=False)
6440         elif cmd == "DAMAGES":                # damage reports
6441             damagereport()
6442         elif cmd == "CHART":                # chart
6443             makechart()
6444         elif cmd == "IMPULSE":                # impulse
6445             impulse()
6446         elif cmd == "REST":                # rest
6447             wait()
6448             if game.ididit:
6449                 hitme = True
6450         elif cmd == "WARP":                # warp
6451             setwarp()
6452         elif cmd == "SENSORS":                # sensors
6453             sensor()
6454         elif cmd == "ORBIT":                # orbit
6455             orbit()
6456             if game.ididit:
6457                 hitme = True
6458         elif cmd == "TRANSPORT":                # transport "beam"
6459             beam()
6460         elif cmd == "MINE":                # mine
6461             mine()
6462             if game.ididit:
6463                 hitme = True
6464         elif cmd == "CRYSTALS":                # crystals
6465             usecrystals()
6466             if game.ididit:
6467                 hitme = True
6468         elif cmd == "SHUTTLE":                # shuttle
6469             shuttle()
6470             if game.ididit:
6471                 hitme = True
6472         elif cmd == "PLANETS":                # Planet list
6473             survey()
6474         elif cmd == "REPORT":                # Game Report
6475             report()
6476         elif cmd == "COMPUTER":                # use COMPUTER!
6477             eta()
6478         elif cmd == "COMMANDS":
6479             listCommands()
6480         elif cmd == "EMEXIT":                # Emergency exit
6481             clrscr()                        # Hide screen
6482             freeze(True)                # forced save
6483             raise SystemExit(1)                # And quick exit
6484         elif cmd == "PROBE":
6485             probe()                        # Launch probe
6486             if game.ididit:
6487                 hitme = True
6488         elif cmd == "ABANDON":                # Abandon Ship
6489             abandon()
6490         elif cmd == "DESTRUCT":                # Self Destruct
6491             selfdestruct()
6492         elif cmd == "SAVE":                # Save Game
6493             freeze(False)
6494             clrscr()
6495             if game.skill > SKILL_GOOD:
6496                 prout(_("WARNING--Saved games produce no plaques!"))
6497         elif cmd == "DEATHRAY":                # Try a desparation measure
6498             deathray()
6499             if game.ididit:
6500                 hitme = True
6501         elif cmd == "CAPTURE":
6502             capture()
6503         elif cmd == "CLOAK":
6504             cloak()
6505         elif cmd == "DEBUGCMD":                # What do we want for debug???
6506             debugme()
6507         elif cmd == "MAYDAY":                # Call for help
6508             mayday()
6509             if game.ididit:
6510                 hitme = True
6511         elif cmd == "QUIT":
6512             game.alldone = True                # quit the game
6513         elif cmd == "HELP":
6514             helpme()                        # get help
6515         elif cmd == "SCORE":
6516             score()                         # see current score
6517         elif cmd == "CURSES":   # pragma: no cover
6518             game.options |= (OPTION_CURSES | OPTION_COLOR)
6519             iostart()
6520         elif cmd == "OPTIONS":
6521             goptions()
6522         while True:
6523             if game.alldone:
6524                 break                # Game has ended
6525             if game.optime != 0.0:
6526                 events()
6527                 if game.alldone:
6528                     break        # Events did us in
6529             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6530                 atover(False)
6531                 continue
6532             if hitme and not game.justin:
6533                 attack(torps_ok=True)
6534                 if game.alldone:
6535                     break
6536                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6537                     atover(False)
6538                     hitme = True
6539                     continue
6540             break
6541         if game.alldone:
6542             break
6543     if game.idebug:
6544         prout("=== Ending")                # pragma: no cover
6545
6546 def cramen(ch):
6547     "Emit the name of an enemy or feature."
6548     if   ch == 'R': s = _("Romulan")
6549     elif ch == 'K': s = _("Klingon")
6550     elif ch == 'C': s = _("Commander")
6551     elif ch == 'S': s = _("Super-commander")
6552     elif ch == '*': s = _("Star")
6553     elif ch == 'P': s = _("Planet")
6554     elif ch == 'B': s = _("Starbase")
6555     elif ch == ' ': s = _("Black hole")
6556     elif ch == 'T': s = _("Tholian")
6557     elif ch == '#': s = _("Tholian web")
6558     elif ch == '?': s = _("Stranger")
6559     elif ch == '@': s = _("Inhabited World")
6560     else: s = "Unknown??"                # pragma: no cover
6561     return s
6562
6563 def crmena(loud, enemy, loctype, w):
6564     "Emit the name of an enemy and his location."
6565     buf = ""
6566     if loud:
6567         buf += "***"
6568     buf += cramen(enemy) + _(" at ")
6569     if loctype == "quadrant":
6570         buf += _("Quadrant ")
6571     elif loctype == "sector":
6572         buf += _("Sector ")
6573     return buf + repr(w)
6574
6575 def crmshp():
6576     "Emit our ship name."
6577     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6578
6579 def stars():
6580     "Emit a line of stars"
6581     prouts("******************************************************")
6582     skip(1)
6583
6584 def expran(avrage):
6585     return -avrage*math.log(1e-7 + rnd.real())
6586
6587 def randplace(size):
6588     "Choose a random location."
6589     w = Coord()
6590     w.i = rnd.integer(size)
6591     w.j = rnd.integer(size)
6592     return w
6593
6594 class sstscanner:
6595     def __init__(self):
6596         self.type = None
6597         self.token = None
6598         self.real = 0.0
6599         self.inqueue = []
6600     def nexttok(self):
6601         # Get a token from the user
6602         self.real = 0.0
6603         self.token = ''
6604         # Fill the token queue if nothing here
6605         while not self.inqueue:
6606             sline = cgetline()
6607             if curwnd==prompt_window:
6608                 clrscr()
6609                 setwnd(message_window)
6610                 clrscr()
6611             if sline == '':
6612                 return None
6613             if not sline:
6614                 continue
6615             else:
6616                 self.inqueue = sline.lstrip().split() + ["\n"]
6617         # From here on in it's all looking at the queue
6618         self.token = self.inqueue.pop(0)
6619         if self.token == "\n":
6620             self.type = "IHEOL"
6621             return "IHEOL"
6622         try:
6623             self.real = float(self.token)
6624             self.type = "IHREAL"
6625             return "IHREAL"
6626         except ValueError:
6627             pass
6628         # Treat as alpha
6629         self.token = self.token.lower()
6630         self.type = "IHALPHA"
6631         self.real = None
6632         return "IHALPHA"
6633     def append(self, tok):
6634         self.inqueue.append(tok)
6635     def push(self, tok):
6636         self.inqueue.insert(0, tok)
6637     def waiting(self):
6638         return self.inqueue
6639     def chew(self):
6640         # Demand input for next scan
6641         self.inqueue = []
6642         self.real = self.token = None
6643     def sees(self, s):
6644         # compares s to item and returns true if it matches to the length of s
6645         return s.startswith(self.token)
6646     def int(self):
6647         # Round token value to nearest integer
6648         return int(round(self.real))
6649     def getcoord(self):
6650         s = Coord()
6651         self.nexttok()
6652         if (game.options & OPTION_ALPHAMERIC):
6653             try:
6654                 if (self.type == "IHALPHA") and (self.token[0] in "abcdefghij") and (self.token[1] in "0123456789"):
6655                     s.i = ord(self.token[0]) - ord("a")
6656                     s.j = int(self.token[1:])-1
6657                     return s
6658             except (TypeError, IndexError):
6659                 huh()
6660                 return None
6661         if self.type != "IHREAL":
6662             huh()
6663             return None
6664         s.i = self.int()-1
6665         self.nexttok()
6666         if self.type != "IHREAL":
6667             huh()
6668             return None
6669         s.j = self.int()-1
6670         return s
6671     def __repr__(self):                # pragma: no cover
6672         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6673
6674 def ja():
6675     "Yes-or-no confirmation."
6676     scanner.chew()
6677     while True:
6678         scanner.nexttok()
6679         if scanner.token == 'y':
6680             return True
6681         if scanner.token == 'n':
6682             return False
6683         scanner.chew()
6684         proutn(_("Please answer with \"y\" or \"n\": "))
6685
6686 def huh():
6687     "Complain about unparseable input."
6688     scanner.chew()
6689     skip(1)
6690     prout(_("Beg your pardon, Captain?"))
6691
6692 def debugme():                # pragma: no cover
6693     "Access to the internals for debugging."
6694     proutn("Reset levels? ")
6695     if ja():
6696         if game.energy < game.inenrg:
6697             game.energy = game.inenrg
6698         game.shield = game.inshld
6699         game.torps = game.intorps
6700         game.lsupres = game.inlsr
6701     proutn("Reset damage? ")
6702     if ja():
6703         for i in range(NDEVICES):
6704             if game.damage[i] > 0.0:
6705                 game.damage[i] = 0.0
6706     proutn("Toggle debug flag? ")
6707     if ja():
6708         game.idebug = not game.idebug
6709         if game.idebug:                # pragma: no cover
6710             prout("Debug output ON")
6711         else:
6712             prout("Debug output OFF")
6713     proutn("Cause selective damage? ")
6714     if ja():
6715         for i in range(NDEVICES):
6716             proutn("Kill %s?" % device[i])
6717             scanner.chew()
6718             key = scanner.nexttok()
6719             if key == "IHALPHA" and scanner.sees("y"):
6720                 game.damage[i] = 10.0
6721     proutn("Examine/change events? ")
6722     if ja():
6723         ev = Event()
6724         w = Coord()
6725         legends = {
6726             FSNOVA:  "Supernova       ",
6727             FTBEAM:  "T Beam          ",
6728             FSNAP:   "Snapshot        ",
6729             FBATTAK: "Base Attack     ",
6730             FCDBAS:  "Base Destroy    ",
6731             FSCMOVE: "SC Move         ",
6732             FSCDBAS: "SC Base Destroy ",
6733             FDSPROB: "Probe Move      ",
6734             FDISTR:  "Distress Call   ",
6735             FENSLV:  "Enslavement     ",
6736             FREPRO:  "Klingon Build   ",
6737         }
6738         for i in range(1, NEVENTS):
6739             proutn(legends[i])
6740             if is_scheduled(i):
6741                 proutn("%.2f" % (scheduled(i)-game.state.date))
6742                 if i in {FENSLV, FREPRO}:
6743                     ev = findevent(i)
6744                     proutn(" in %s" % ev.quadrant)
6745             else:
6746                 proutn("never")
6747             proutn("? ")
6748             scanner.chew()
6749             key = scanner.nexttok()
6750             if key == 'n':
6751                 unschedule(i)
6752                 scanner.chew()
6753             elif key == "IHREAL":
6754                 ev = schedule(i, scanner.real)
6755                 if i in {FENSLV, FREPRO}:
6756                     scanner.chew()
6757                     proutn("In quadrant- ")
6758                     key = scanner.nexttok()
6759                     # "IHEOL" says to leave coordinates as they are
6760                     if key != "IHEOL":
6761                         if key != "IHREAL":
6762                             prout("Event %d canceled, no x coordinate." % (i))
6763                             unschedule(i)
6764                             continue
6765                         w.i = int(round(scanner.real))
6766                         key = scanner.nexttok()
6767                         if key != "IHREAL":
6768                             prout("Event %d canceled, no y coordinate." % (i))
6769                             unschedule(i)
6770                             continue
6771                         w.j = int(round(scanner.real))
6772                         ev.quadrant = w
6773         scanner.chew()
6774     proutn("Induce supernova here? ")
6775     if ja():
6776         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6777         atover(True)
6778
6779 if __name__ == '__main__':
6780     try:
6781         #global line, thing, game
6782         game = None
6783         thing = Thingy()
6784         game = Gamestate()
6785         rnd = randomizer()
6786         logfp = None
6787         game.options = OPTION_ALL &~ OPTION_IOMODES
6788         if os.getenv("TERM"):
6789             game.options |= OPTION_CURSES       # pragma: no cover
6790         else:
6791             game.options |= OPTION_TTY
6792         seed = int(time.time())
6793         try:
6794             (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
6795             for (switch, val) in options:
6796                 if switch == '-r':
6797                     # pylint: disable=raise-missing-from
6798                     try:
6799                         replayfp = open(val, "r")
6800                     except IOError:
6801                         sys.stderr.write("sst: can't open replay file %s\n" % val)
6802                         raise SystemExit(1)
6803                     # pylint: disable=raise-missing-from
6804                     try:
6805                         line = replayfp.readline().strip()
6806                         (leader, __, seed) = line.split()
6807                         # pylint: disable=eval-used
6808                         seed = eval(seed)
6809                         line = replayfp.readline().strip()
6810                         arguments += line.split()[2:]
6811                     except ValueError:                # pragma: no cover
6812                         sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6813                         raise SystemExit(1)
6814                     game.options |= OPTION_TTY
6815                     game.options &=~ OPTION_CURSES
6816                 elif switch == '-s':                # pragma: no cover
6817                     seed = int(val)
6818                 elif switch == '-t':    # pragma: no cover
6819                     game.options |= OPTION_TTY
6820                     game.options &=~ OPTION_CURSES
6821                 elif switch == '-x':                # pragma: no cover
6822                     game.idebug = True
6823                 elif switch == '-c':    # Enable curses debugging - undocumented
6824                     game.cdebug = True
6825                 elif switch == '-V':                # pragma: no cover
6826                     print("SST2K", version)
6827                     raise SystemExit(0)
6828                 else:                # pragma: no cover
6829                     sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6830                     raise SystemExit(1)
6831         except getopt.GetoptError as err:
6832             print(err)
6833             raise SystemExit(1) from err
6834         # where to save the input in case of bugs
6835         if "TMPDIR" in os.environ:                # pragma: no cover
6836             tmpdir = os.environ['TMPDIR']
6837         else:
6838             tmpdir = "/tmp"
6839         try:
6840             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6841         except IOError:                # pragma: no cover
6842             sys.stderr.write("sst: warning, can't open logfile\n")
6843             sys.exit(1)
6844         if logfp:
6845             logfp.write("# seed %s\n" % seed)
6846             logfp.write("# arguments %s\n" % " ".join(arguments))
6847             logfp.write("# SST2K version %s\n" % version)
6848             logfp.write("# recorded by %s@%s on %s\n" % \
6849                     (getpass.getuser(),socket.getfqdn(),time.ctime()))
6850             logfp.write("#\n")
6851         rnd.seed(seed)
6852         scanner = sstscanner()
6853         for arg in arguments:
6854             scanner.append(arg)
6855         try:
6856             iostart()
6857             while True: # Play a game
6858                 setwnd(fullscreen_window)
6859                 clrscr()
6860                 prelim()
6861                 setup()
6862                 if game.alldone:
6863                     score()
6864                     game.alldone = False
6865                 else:
6866                     makemoves()
6867                 if replayfp:
6868                     break
6869                 skip(1)
6870                 if (game.options & OPTION_TTY):
6871                     stars()
6872                 skip(1)
6873                 if game.tourn and game.alldone:
6874                     proutn(_("Do you want your score recorded?"))
6875                     if ja():
6876                         scanner.chew()
6877                         scanner.push("\n")
6878                         freeze(False)
6879                 scanner.chew()
6880                 if (game.options & OPTION_TTY):
6881                     proutn(_("Do you want to play again? "))
6882                     if not ja():
6883                         break
6884                 else:
6885                     break
6886             skip(1)
6887             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6888         finally:
6889             ioend()
6890         raise SystemExit(0)
6891     except KeyboardInterrupt:                # pragma: no cover
6892         if logfp:
6893             logfp.close()
6894         print("")
6895
6896 # End.