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