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