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