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