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