b9d9a6acf9c3467a64676c7bcca2962f8450dbc0
[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, locale
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     # for some recent versions of python2, the following enables UTF8
3416     # for the older ones we probably need to set C locale, and python3
3417     # has no problems at all
3418     if sys.version_info[0] < 3:
3419         locale.setlocale(locale.LC_ALL, "")
3420     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3421     gettext.textdomain("sst")
3422     if not (game.options & OPTION_CURSES):
3423         ln_env = os.getenv("LINES")
3424         if ln_env:
3425             rows = ln_env
3426         else:
3427             rows = 25
3428     else:
3429         stdscr = curses.initscr()
3430         stdscr.keypad(True)
3431         curses.nonl()
3432         curses.cbreak()
3433         if game.options & OPTION_COLOR:
3434             curses.start_color()
3435             curses.use_default_colors()
3436             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3437             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3438             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3439             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3440             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3441             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3442             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3443             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3444         global fullscreen_window, srscan_window, report_window, status_window
3445         global lrscan_window, message_window, prompt_window
3446         (rows, _columns)   = stdscr.getmaxyx()
3447         fullscreen_window = stdscr
3448         srscan_window     = curses.newwin(12, 25, 0,       0)
3449         report_window     = curses.newwin(11, 0,  1,       25)
3450         status_window     = curses.newwin(10, 0,  1,       39)
3451         lrscan_window     = curses.newwin(5,  0,  0,       64)
3452         message_window    = curses.newwin(0,  0,  12,      0)
3453         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3454         message_window.scrollok(True)
3455         setwnd(fullscreen_window)
3456
3457 def ioend():
3458     "Wrap up I/O."
3459     if game.options & OPTION_CURSES:
3460         stdscr.keypad(False)
3461         curses.echo()
3462         curses.nocbreak()
3463         curses.endwin()
3464
3465 def waitfor():
3466     "Wait for user action -- OK to do nothing if on a TTY"
3467     if game.options & OPTION_CURSES:
3468         stdscr.getch()
3469
3470 def announce():
3471     skip(1)
3472     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3473     skip(1)
3474
3475 def pause_game():
3476     if game.skill > SKILL_FAIR:
3477         prompt = _("[CONTINUE?]")
3478     else:
3479         prompt = _("[PRESS ENTER TO CONTINUE]")
3480
3481     if game.options & OPTION_CURSES:
3482         drawmaps(0)
3483         setwnd(prompt_window)
3484         prompt_window.clear()
3485         prompt_window.addstr(prompt)
3486         prompt_window.getstr()
3487         prompt_window.clear()
3488         prompt_window.refresh()
3489         setwnd(message_window)
3490     else:
3491         global linecount
3492         sys.stdout.write('\n')
3493         proutn(prompt)
3494         if not replayfp:
3495             input()
3496         sys.stdout.write('\n' * rows)
3497         linecount = 0
3498
3499 def skip(i):
3500     "Skip i lines.  Pause game if this would cause a scrolling event."
3501     for _dummy in range(i):
3502         if game.options & OPTION_CURSES:
3503             (y, _x) = curwnd.getyx()
3504             try:
3505                 curwnd.move(y+1, 0)
3506             except curses.error:
3507                 pass
3508         else:
3509             global linecount
3510             linecount += 1
3511             if rows and linecount >= rows:
3512                 pause_game()
3513             else:
3514                 sys.stdout.write('\n')
3515
3516 def proutn(proutntline):
3517     "Utter a line with no following line feed."
3518     if game.options & OPTION_CURSES:
3519         (y, x) = curwnd.getyx()
3520         (my, _mx) = curwnd.getmaxyx()
3521         if curwnd == message_window and y >= my - 2:
3522             pause_game()
3523             clrscr()
3524         if logfp and game.cdebug:
3525             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3526         curwnd.addstr(proutntline)
3527         curwnd.refresh()
3528     else:
3529         sys.stdout.write(proutntline)
3530         sys.stdout.flush()
3531
3532 def prout(proutline):
3533     proutn(proutline)
3534     skip(1)
3535
3536 def prouts(proutsline):
3537     "Emit slowly!"
3538     for c in proutsline:
3539         if not replayfp or replayfp.closed:        # Don't slow down replays
3540             time.sleep(0.03)
3541         proutn(c)
3542         if game.options & OPTION_CURSES:
3543             curwnd.refresh()
3544         else:
3545             sys.stdout.flush()
3546     if not replayfp or replayfp.closed:
3547         time.sleep(0.03)
3548
3549 def cgetline():
3550     "Get a line of input."
3551     if game.options & OPTION_CURSES:
3552         linein = codecs.decode(curwnd.getstr()) + "\n"
3553         curwnd.refresh()
3554     else:
3555         if replayfp and not replayfp.closed:
3556             while True:
3557                 linein = replayfp.readline()
3558                 proutn(linein)
3559                 if linein == '':
3560                     prout("*** Replay finished")
3561                     replayfp.close()
3562                     break
3563                 elif linein[0] != "#":
3564                     break
3565         else:
3566             try:
3567                 linein = input() + "\n"
3568             except EOFError:
3569                 prout("")
3570                 sys.exit(0)
3571     if logfp:
3572         logfp.write(linein)
3573     return linein
3574
3575 def setwnd(wnd):
3576     "Change windows -- OK for this to be a no-op in tty mode."
3577     global curwnd
3578     if game.options & OPTION_CURSES:
3579         if game.cdebug and logfp:
3580             if wnd == fullscreen_window:
3581                 legend = "fullscreen"
3582             elif wnd == srscan_window:
3583                 legend = "srscan"
3584             elif wnd == report_window:
3585                 legend = "report"
3586             elif wnd == status_window:
3587                 legend = "status"
3588             elif wnd == lrscan_window:
3589                 legend = "lrscan"
3590             elif wnd == message_window:
3591                 legend = "message"
3592             elif wnd == prompt_window:
3593                 legend = "prompt"
3594             else:
3595                 legend = "unknown"
3596             logfp.write("#curses: setwnd(%s)\n" % legend)
3597         curwnd = wnd
3598         # Some curses implementations get confused when you try this.
3599         try:
3600             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3601         except curses.error:
3602             pass
3603
3604 def clreol():
3605     "Clear to end of line -- can be a no-op in tty mode"
3606     if game.options & OPTION_CURSES:
3607         curwnd.clrtoeol()
3608         curwnd.refresh()
3609
3610 def clrscr():
3611     "Clear screen -- can be a no-op in tty mode."
3612     global linecount
3613     if game.options & OPTION_CURSES:
3614         curwnd.clear()
3615         curwnd.move(0, 0)
3616         curwnd.refresh()
3617     linecount = 0
3618
3619 def textcolor(color=DEFAULT):
3620     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3621         if color == DEFAULT:
3622             curwnd.attrset(0)
3623         elif color ==  BLACK:
3624             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3625         elif color ==  BLUE:
3626             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3627         elif color ==  GREEN:
3628             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3629         elif color ==  CYAN:
3630             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3631         elif color ==  RED:
3632             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3633         elif color ==  MAGENTA:
3634             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3635         elif color ==  BROWN:
3636             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3637         elif color ==  LIGHTGRAY:
3638             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3639         elif color ==  DARKGRAY:
3640             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3641         elif color ==  LIGHTBLUE:
3642             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3643         elif color ==  LIGHTGREEN:
3644             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3645         elif color ==  LIGHTCYAN:
3646             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3647         elif color ==  LIGHTRED:
3648             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3649         elif color ==  LIGHTMAGENTA:
3650             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3651         elif color ==  YELLOW:
3652             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3653         elif color ==  WHITE:
3654             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3655
3656 def highvideo():
3657     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3658         curwnd.attron(curses.A_REVERSE)
3659
3660 #
3661 # Things past this point have policy implications.
3662 #
3663
3664 def drawmaps(mode):
3665     "Hook to be called after moving to redraw maps."
3666     if game.options & OPTION_CURSES:
3667         if mode == 1:
3668             sensor()
3669         setwnd(srscan_window)
3670         curwnd.move(0, 0)
3671         srscan()
3672         if mode != 2:
3673             setwnd(status_window)
3674             status_window.clear()
3675             status_window.move(0, 0)
3676             setwnd(report_window)
3677             report_window.clear()
3678             report_window.move(0, 0)
3679             status()
3680             setwnd(lrscan_window)
3681             lrscan_window.clear()
3682             lrscan_window.move(0, 0)
3683             lrscan(silent=False)
3684
3685 def put_srscan_sym(w, sym):
3686     "Emit symbol for short-range scan."
3687     srscan_window.move(w.i+1, w.j*2+2)
3688     srscan_window.addch(sym)
3689     srscan_window.refresh()
3690
3691 def boom(w):
3692     "Enemy fall down, go boom."
3693     if game.options & OPTION_CURSES:
3694         drawmaps(0)
3695         setwnd(srscan_window)
3696         srscan_window.attron(curses.A_REVERSE)
3697         put_srscan_sym(w, game.quad[w.i][w.j])
3698         #sound(500)
3699         #time.sleep(1.0)
3700         #nosound()
3701         srscan_window.attroff(curses.A_REVERSE)
3702         put_srscan_sym(w, game.quad[w.i][w.j])
3703         curses.delay_output(500)
3704         setwnd(message_window)
3705
3706 def warble():
3707     "Sound and visual effects for teleportation."
3708     if game.options & OPTION_CURSES:
3709         drawmaps(2)
3710         setwnd(message_window)
3711         #sound(50)
3712     prouts("     . . . . .     ")
3713     if game.options & OPTION_CURSES:
3714         #curses.delay_output(1000)
3715         #nosound()
3716         pass
3717
3718 def tracktorpedo(w, step, i, n, iquad):
3719     "Torpedo-track animation."
3720     if not game.options & OPTION_CURSES:
3721         if step == 1:
3722             if n != 1:
3723                 skip(1)
3724                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3725             else:
3726                 skip(1)
3727                 proutn(_("Torpedo track- "))
3728         elif step in {4, 9}:
3729             skip(1)
3730         proutn("%s   " % w)
3731     else:
3732         if not damaged(DSRSENS) or game.condition=="docked":
3733             if i != 0 and step == 1:
3734                 drawmaps(2)
3735                 time.sleep(0.4)
3736             if iquad in {'.', ' '}:
3737                 put_srscan_sym(w, '+')
3738                 #sound(step*10)
3739                 #time.sleep(0.1)
3740                 #nosound()
3741                 put_srscan_sym(w, iquad)
3742             else:
3743                 curwnd.attron(curses.A_REVERSE)
3744                 put_srscan_sym(w, iquad)
3745                 #sound(500)
3746                 #time.sleep(1.0)
3747                 #nosound()
3748                 curwnd.attroff(curses.A_REVERSE)
3749                 put_srscan_sym(w, iquad)
3750         else:
3751             proutn("%s   " % w)
3752
3753 def makechart():
3754     "Display the current galaxy chart."
3755     if game.options & OPTION_CURSES:
3756         setwnd(message_window)
3757         message_window.clear()
3758     chart()
3759     if game.options & OPTION_TTY:
3760         skip(1)
3761
3762 NSYM        = 14
3763
3764 def prstat(txt, data):
3765     proutn(txt)
3766     if game.options & OPTION_CURSES:
3767         skip(1)
3768         setwnd(status_window)
3769     else:
3770         proutn(" " * (NSYM - len(txt)))
3771     proutn(data)
3772     skip(1)
3773     if game.options & OPTION_CURSES:
3774         setwnd(report_window)
3775
3776 # Code from moving.c begins here
3777
3778 def imove(icourse=None, noattack=False):
3779     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3780     w = Coord()
3781
3782     def newquadrant(noattack):
3783         # Leaving quadrant -- allow final enemy attack
3784         # Don't set up attack if being pushed by nova or cloaked
3785         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3786             newcnd()
3787             for enemy in game.enemies:
3788                 finald = (w - enemy.location).distance()
3789                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3790             # Stas Sergeev added the condition
3791             # that attacks only happen if Klingons
3792             # are present and your skill is good.
3793             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3794                 attack(torps_ok=False)
3795             if game.alldone:
3796                 return
3797         # check for edge of galaxy
3798         kinks = 0
3799         while True:
3800             kink = False
3801             if icourse.final.i < 0:
3802                 icourse.final.i = -icourse.final.i
3803                 kink = True
3804             if icourse.final.j < 0:
3805                 icourse.final.j = -icourse.final.j
3806                 kink = True
3807             if icourse.final.i >= GALSIZE*QUADSIZE:
3808                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3809                 kink = True
3810             if icourse.final.j >= GALSIZE*QUADSIZE:
3811                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3812                 kink = True
3813             if kink:
3814                 kinks += 1
3815             else:
3816                 break
3817         if kinks:
3818             game.nkinks += 1
3819             if game.nkinks == 3:
3820                 # Three strikes -- you're out!
3821                 finish(FNEG3)
3822                 return
3823             skip(1)
3824             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3825             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3826             prout(_("YOU WILL BE DESTROYED."))
3827         # Compute final position in new quadrant
3828         if trbeam: # Don't bother if we are to be beamed
3829             return
3830         game.quadrant = icourse.final.quadrant()
3831         game.sector = icourse.final.sector()
3832         skip(1)
3833         prout(_("Entering Quadrant %s.") % game.quadrant)
3834         game.quad[game.sector.i][game.sector.j] = game.ship
3835         newqad()
3836         if game.skill>SKILL_NOVICE:
3837             attack(torps_ok=False)
3838
3839     def check_collision(h):
3840         iquad = game.quad[h.i][h.j]
3841         if iquad != '.':
3842             # object encountered in flight path
3843             stopegy = 50.0*icourse.distance/game.optime
3844             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3845                 for enemy in game.enemies:
3846                     if enemy.location == h:
3847                         collision(rammed=False, enemy=enemy)
3848                         return True
3849                 # This should not happen
3850                 prout(_("Which way did he go?")) # pragma: no cover
3851                 return False
3852             elif iquad == ' ':
3853                 skip(1)
3854                 prouts(_("***RED ALERT!  RED ALERT!"))
3855                 skip(1)
3856                 proutn("***" + crmshp())
3857                 proutn(_(" pulled into black hole at Sector %s") % h)
3858                 # Getting pulled into a black hole was certain
3859                 # death in Almy's original.  Stas Sergeev added a
3860                 # possibility that you'll get timewarped instead.
3861                 n=0
3862                 for m in range(NDEVICES):
3863                     if game.damage[m]>0:
3864                         n += 1
3865                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3866                 if (game.options & OPTION_BLKHOLE) and rnd.withprob(1-probf):
3867                     timwrp()
3868                 else:
3869                     finish(FHOLE)
3870                 return True
3871             else:
3872                 # something else
3873                 skip(1)
3874                 proutn(crmshp())
3875                 if iquad == '#':
3876                     prout(_(" encounters Tholian web at %s;") % h)
3877                 else:
3878                     prout(_(" blocked by object at %s;") % h)
3879                 proutn(_("Emergency stop required "))
3880                 prout(_("%2d units of energy.") % int(stopegy))
3881                 game.energy -= stopegy
3882                 if game.energy <= 0:
3883                     finish(FNRG)
3884                 return True
3885         return False
3886
3887     trbeam = False
3888     if game.inorbit:
3889         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3890         game.inorbit = False
3891     # If tractor beam is to occur, don't move full distance
3892     if game.state.date+game.optime >= scheduled(FTBEAM):
3893         if game.iscloaked:
3894             # We can't be tractor beamed if cloaked,
3895             # so move the event into the future
3896             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
3897         else:
3898             trbeam = True
3899             game.condition = "red"
3900             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3901             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3902     # Move out
3903     game.quad[game.sector.i][game.sector.j] = '.'
3904     for _m in range(icourse.moves):
3905         icourse.nexttok()
3906         w = icourse.sector()
3907         if icourse.origin.quadrant() != icourse.location.quadrant():
3908             newquadrant(noattack)
3909             break
3910         elif check_collision(w):
3911             prout(_("Collision detected"))
3912             break
3913         else:
3914             game.sector = w
3915     # We're in destination quadrant -- compute new average enemy distances
3916     game.quad[game.sector.i][game.sector.j] = game.ship
3917     if game.enemies:
3918         for enemy in game.enemies:
3919             finald = (w-enemy.location).distance()
3920             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3921             enemy.kdist = finald
3922         sortenemies()
3923         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3924             attack(torps_ok=False)
3925         for enemy in game.enemies:
3926             enemy.kavgd = enemy.kdist
3927     newcnd()
3928     drawmaps(0)
3929     setwnd(message_window)
3930
3931 def dock(verbose):
3932     "Dock our ship at a starbase."
3933     scanner.chew()
3934     if game.condition == "docked" and verbose:
3935         prout(_("Already docked."))
3936         return
3937     if game.inorbit:
3938         prout(_("You must first leave standard orbit."))
3939         return
3940     if game.base is None or not game.base.valid_sector():
3941         prout(_("No starbase available for docking in this quadrant."))
3942         return
3943     if (abs(game.sector.i-game.base.i) > 1) or (abs(game.sector.j-game.base.j) > 1):
3944         prout(crmshp() + _(" not adjacent to base."))
3945         return
3946     if game.iscloaked:
3947         prout(_("You cannot dock while cloaked."))
3948         return
3949     game.condition = "docked"
3950     if verbose:
3951         prout(_("Docked."))
3952     game.ididit = True
3953     if game.energy < game.inenrg:
3954         game.energy = game.inenrg
3955     game.shield = game.inshld
3956     game.torps = game.intorps
3957     game.lsupres = game.inlsr
3958     game.state.crew = FULLCREW
3959     if game.brigcapacity-game.brigfree > 0:
3960         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3961         game.kcaptured += game.brigcapacity-game.brigfree
3962         game.brigfree = game.brigcapacity
3963     if communicating() and \
3964         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3965         # get attack report from base
3966         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3967         attackreport(False)
3968         game.iseenit = True
3969
3970 def cartesian(loc1=None, loc2=None):
3971     if loc1 is None:
3972         return game.quadrant * QUADSIZE + game.sector
3973     elif loc2 is None:
3974         return game.quadrant * QUADSIZE + loc1
3975     else:
3976         return loc1 * QUADSIZE + loc2
3977
3978 def getcourse(isprobe):
3979     "Get a course and distance from the user."
3980     key = ""
3981     dquad = copy.copy(game.quadrant)
3982     navmode = "unspecified"
3983     itemp = "curt"
3984     dsect = Coord()
3985     iprompt = False
3986     if game.landed and not isprobe:
3987         prout(_("Dummy! You can't leave standard orbit until you"))
3988         proutn(_("are back aboard the ship."))
3989         scanner.chew()
3990         raise TrekError
3991     while navmode == "unspecified":
3992         if damaged(DNAVSYS):
3993             if isprobe:
3994                 prout(_("Computer damaged; manual navigation only"))
3995             else:
3996                 prout(_("Computer damaged; manual movement only"))
3997             scanner.chew()
3998             navmode = "manual"
3999             key = "IHEOL"
4000             break
4001         key = scanner.nexttok()
4002         if key == "IHEOL":
4003             proutn(_("Manual or automatic- "))
4004             iprompt = True
4005             scanner.chew()
4006         elif key == "IHALPHA":
4007             if scanner.sees("manual"):
4008                 navmode = "manual"
4009                 key = scanner.nexttok()
4010                 break
4011             elif scanner.sees("automatic"):
4012                 navmode = "automatic"
4013                 key = scanner.nexttok()
4014                 break
4015             else:
4016                 huh()
4017                 scanner.chew()
4018                 raise TrekError
4019         else: # numeric
4020             if isprobe:
4021                 prout(_("(Manual navigation assumed.)"))
4022             else:
4023                 prout(_("(Manual movement assumed.)"))
4024             navmode = "manual"
4025             break
4026     delta = Coord()
4027     if navmode == "automatic":
4028         while key == "IHEOL":
4029             if isprobe:
4030                 proutn(_("Target quadrant or quadrant&sector- "))
4031             else:
4032                 proutn(_("Destination sector or quadrant&sector- "))
4033             scanner.chew()
4034             iprompt = True
4035             key = scanner.nexttok()
4036         scanner.push(scanner.token)     # Something IHREAL or IHALPHA awaits us
4037         first = scanner.getcoord()
4038         if first is None:
4039             raise TrekError
4040         scanner.nexttok()
4041         if scanner.type == "IHEOL":
4042             second = None
4043         else:
4044             scanner.push(scanner.token)
4045             second = scanner.getcoord()
4046             if second is None:
4047                 raise TrekError
4048         scanner.chew()
4049         if second is not None:
4050             dquad.i = first.i
4051             dquad.j = first.j
4052             dsect.i = second.i
4053             dsect.j = second.j
4054         else:
4055             # only one pair of numbers was specified
4056             if isprobe:
4057                 # only quadrant specified -- go to center of dest quad
4058                 dquad.i = first.i
4059                 dquad.j = first.j
4060                 dsect.j = dsect.i = (QUADSIZE/2)-1        # preserves 1-origin behavior
4061             else:
4062                 # only sector specified
4063                 dsect.i = first.i
4064                 dsect.j = first.j
4065             itemp = "normal"
4066         if not dquad.valid_quadrant() or not dsect.valid_sector():
4067             huh()
4068             raise TrekError
4069         skip(1)
4070         if not isprobe:
4071             if itemp > "curt":
4072                 if iprompt:
4073                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
4074             else:
4075                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
4076         # the actual deltas get computed here
4077         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
4078         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
4079     else: # manual
4080         while key == "IHEOL":
4081             proutn(_("X and Y displacements- "))
4082             scanner.chew()
4083             iprompt = True
4084             key = scanner.nexttok()
4085         itemp = "verbose"
4086         if key == "IHREAL":
4087             delta.j = scanner.real
4088         else:
4089             huh()
4090             raise TrekError
4091         key = scanner.nexttok()
4092         if key == "IHREAL":
4093             delta.i = scanner.real
4094         elif key == "IHEOL":
4095             delta.i = 0
4096             scanner.push("\n")
4097         else:
4098             huh()
4099             raise TrekError
4100     # Check for zero movement
4101     if delta.i == 0 and delta.j == 0:
4102         scanner.chew()
4103         raise TrekError
4104     if itemp == "verbose" and not isprobe:
4105         skip(1)
4106         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4107     scanner.chew()
4108     return course(bearing=delta.bearing(), distance=delta.distance())
4109
4110 class course:
4111     def __init__(self, bearing, distance, origin=None):
4112         self.distance = distance
4113         self.bearing = bearing
4114         if origin is None:
4115             self.origin = cartesian(game.quadrant, game.sector)
4116         else:
4117             self.origin = origin
4118         # The bearing() code we inherited from FORTRAN is actually computing
4119         # clockface directions!
4120         if self.bearing < 0.0:
4121             self.bearing += 12.0
4122         self.angle = ((15.0 - self.bearing) * 0.5235988)
4123         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4124         bigger = max(abs(self.increment.i), abs(self.increment.j))
4125         self.increment /= bigger
4126         self.moves = int(round(10*self.distance*bigger))
4127         self.reset()
4128         self.final = (self.location + self.moves*self.increment).roundtogrid()
4129         self.location = self.origin
4130         self.nextlocation = None
4131     def reset(self):
4132         self.location = self.origin
4133         self.step = 0
4134     def arrived(self):
4135         return self.location.roundtogrid() == self.final
4136     def nexttok(self):
4137         "Next step on course."
4138         self.step += 1
4139         self.nextlocation = self.location + self.increment
4140         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4141         self.location = self.nextlocation
4142         return samequad
4143     def quadrant(self):
4144         return self.location.quadrant()
4145     def sector(self):
4146         return self.location.sector()
4147     def power(self, w):
4148         return self.distance*(w**3)*(game.shldup+1)
4149     def time(self, w):
4150         return 10.0*self.distance/w**2
4151
4152 def impulse():
4153     "Move under impulse power."
4154     game.ididit = False
4155     if damaged(DIMPULS):
4156         scanner.chew()
4157         skip(1)
4158         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4159         return
4160     if game.energy > 30.0:
4161         try:
4162             icourse = getcourse(isprobe=False)
4163         except TrekError:
4164             return
4165         power = 20.0 + 100.0*icourse.distance
4166     else:
4167         power = 30.0
4168     if power >= game.energy:
4169         # Insufficient power for trip
4170         skip(1)
4171         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4172         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4173         if game.energy > 30:
4174             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4175                    int(0.01 * (game.energy-20.0)-0.05))
4176             prout(_(" quadrants.\""))
4177         else:
4178             prout(_("quadrant.  They are, therefore, useless.\""))
4179         scanner.chew()
4180         return
4181     # Make sure enough time is left for the trip
4182     game.optime = icourse.distance/0.095
4183     if game.optime >= game.state.remtime:
4184         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4185         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4186         proutn(_("we dare spend the time?\" "))
4187         if not ja():
4188             return
4189     # Activate impulse engines and pay the cost
4190     imove(icourse, noattack=False)
4191     game.ididit = True
4192     if game.alldone:
4193         return
4194     power = 20.0 + 100.0*icourse.distance
4195     game.energy -= power
4196     game.optime = icourse.distance/0.095
4197     if game.energy <= 0:
4198         finish(FNRG)
4199     return
4200
4201 def warp(wcourse, involuntary):
4202     "ove under warp drive."
4203     blooey = False; twarp = False
4204     if not involuntary: # Not WARPX entry
4205         game.ididit = False
4206         if game.iscloaked:
4207             scanner.chew()
4208             skip(1)
4209             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4210             return
4211         if game.damage[DWARPEN] > 10.0:
4212             scanner.chew()
4213             skip(1)
4214             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4215             return
4216         if damaged(DWARPEN) and game.warpfac > 4.0:
4217             scanner.chew()
4218             skip(1)
4219             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4220             prout(_("  is repaired, I can only give you warp 4.\""))
4221             return
4222                # Read in course and distance
4223         if wcourse is None:
4224             try:
4225                 wcourse = getcourse(isprobe=False)
4226             except TrekError:
4227                 return
4228         # Make sure starship has enough energy for the trip
4229         # Note: this formula is slightly different from the C version,
4230         # and lets you skate a bit closer to the edge.
4231         if wcourse.power(game.warpfac) >= game.energy:
4232             # Insufficient power for trip
4233             game.ididit = False
4234             skip(1)
4235             prout(_("Engineering to bridge--"))
4236             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4237                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4238                 if iwarp <= 0:
4239                     prout(_("We can't do it, Captain. We don't have enough energy."))
4240                 else:
4241                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4242                     if game.shldup:
4243                         prout(",")
4244                         prout(_("if you'll lower the shields."))
4245                     else:
4246                         prout(".")
4247             else:
4248                 prout(_("We haven't the energy to go that far with the shields up."))
4249             return
4250         # Make sure enough time is left for the trip
4251         game.optime = wcourse.time(game.warpfac)
4252         if game.optime >= 0.8*game.state.remtime:
4253             skip(1)
4254             prout(_("First Officer Spock- \"Captain, I compute that such"))
4255             proutn(_("  a trip would require approximately %2.0f") %
4256                    (100.0*game.optime/game.state.remtime))
4257             prout(_(" percent of our"))
4258             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4259             if not ja():
4260                 game.ididit = False
4261                 game.optime=0
4262                 return
4263     # Entry WARPX
4264     if game.warpfac > 6.0:
4265         # Decide if engine damage will occur
4266         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4267         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4268         if prob > rnd.real():
4269             blooey = True
4270             wcourse.distance = rnd.real(wcourse.distance)
4271         # Decide if time warp will occur
4272         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > rnd.real():
4273             twarp = True
4274         if game.idebug and game.warpfac==10 and not twarp:
4275             blooey = False
4276             proutn("=== Force time warp? ")
4277             if ja():
4278                 twarp = True
4279         if blooey or twarp:
4280             # If time warp or engine damage, check path
4281             # If it is obstructed, don't do warp or damage
4282             look = wcourse.moves
4283             while look > 0:
4284                 look -= 1
4285                 wcourse.nexttok()
4286                 w = wcourse.sector()
4287                 if not w.valid_sector():
4288                     break
4289                 if game.quad[w.i][w.j] != '.':
4290                     blooey = False
4291                     twarp = False
4292             wcourse.reset()
4293     # Activate Warp Engines and pay the cost
4294     imove(wcourse, noattack=False)
4295     if game.alldone:
4296         return
4297     game.energy -= wcourse.power(game.warpfac)
4298     if game.energy <= 0:
4299         finish(FNRG)
4300     game.optime = wcourse.time(game.warpfac)
4301     if twarp:
4302         timwrp()
4303     if blooey:
4304         game.damage[DWARPEN] = game.damfac * rnd.real(1.0, 4.0)
4305         skip(1)
4306         prout(_("Engineering to bridge--"))
4307         prout(_("  Scott here.  The warp engines are damaged."))
4308         prout(_("  We'll have to reduce speed to warp 4."))
4309     game.ididit = True
4310     return
4311
4312 def setwarp():
4313     "Change the warp factor."
4314     while True:
4315         key=scanner.nexttok()
4316         if key != "IHEOL":
4317             break
4318         scanner.chew()
4319         proutn(_("Warp factor- "))
4320     if key != "IHREAL":
4321         huh()
4322         return
4323     if game.damage[DWARPEN] > 10.0:
4324         prout(_("Warp engines inoperative."))
4325         return
4326     if damaged(DWARPEN) and scanner.real > 4.0:
4327         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4328         prout(_("  but right now we can only go warp 4.\""))
4329         return
4330     if scanner.real > 10.0:
4331         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4332         return
4333     if scanner.real < 1.0:
4334         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4335         return
4336     oldfac = game.warpfac
4337     game.warpfac = scanner.real
4338     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4339         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4340               int(game.warpfac))
4341         return
4342     if game.warpfac < 8.00:
4343         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4344         return
4345     if game.warpfac == 10.0:
4346         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4347         return
4348     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4349     return
4350
4351 def atover(igrab):
4352     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4353     scanner.chew()
4354     # is captain on planet?
4355     if game.landed:
4356         if damaged(DTRANSP):
4357             finish(FPNOVA)
4358             return
4359         prout(_("Scotty rushes to the transporter controls."))
4360         if game.shldup:
4361             prout(_("But with the shields up it's hopeless."))
4362             finish(FPNOVA)
4363         prouts(_("His desperate attempt to rescue you . . ."))
4364         if rnd.withprob(0.5):
4365             prout(_("fails."))
4366             finish(FPNOVA)
4367             return
4368         prout(_("SUCCEEDS!"))
4369         if game.imine:
4370             game.imine = False
4371             proutn(_("The crystals mined were "))
4372             if rnd.withprob(0.25):
4373                 prout(_("lost."))
4374             else:
4375                 prout(_("saved."))
4376                 game.icrystl = True
4377     if igrab:
4378         return
4379     # Check to see if captain in shuttle craft
4380     if game.icraft:
4381         finish(FSTRACTOR)
4382     if game.alldone:
4383         return
4384     # Inform captain of attempt to reach safety
4385     skip(1)
4386     while True:
4387         if game.justin:
4388             prouts(_("***RED ALERT!  RED ALERT!"))
4389             skip(1)
4390             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4391             prouts(_("   a supernova."))
4392             skip(2)
4393         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4394         prout(_("safely out of quadrant."))
4395         if not damaged(DRADIO):
4396             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4397         # Try to use warp engines
4398         if damaged(DWARPEN):
4399             skip(1)
4400             prout(_("Warp engines damaged."))
4401             finish(FSNOVAED)
4402             return
4403         game.warpfac = rnd.real(6.0, 8.0)
4404         prout(_("Warp factor set to %d") % int(game.warpfac))
4405         power = 0.75*game.energy
4406         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4407         dist = max(dist, rnd.real(math.sqrt(2)))
4408         bugout = course(bearing=rnd.real(12), distance=dist)        # How dumb!
4409         game.optime = bugout.time(game.warpfac)
4410         game.justin = False
4411         game.inorbit = False
4412         warp(bugout, involuntary=True)
4413         if not game.justin:
4414             # This is bad news, we didn't leave quadrant.
4415             if game.alldone:
4416                 return
4417             skip(1)
4418             prout(_("Insufficient energy to leave quadrant."))
4419             finish(FSNOVAED)
4420             return
4421         # Repeat if another snova
4422         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4423             break
4424     if game.unwon()==0:
4425         finish(FWON) # Snova killed remaining enemy.
4426
4427 def timwrp():
4428     "Let's do the time warp again."
4429     prout(_("***TIME WARP ENTERED."))
4430     if game.state.snap and rnd.withprob(0.5):
4431         # Go back in time
4432         prout(_("You are traveling backwards in time %d stardates.") %
4433               int(game.state.date-game.snapsht.date))
4434         game.state = game.snapsht
4435         game.state.snap = False
4436         if len(game.state.kcmdr):
4437             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4438             schedule(FBATTAK, expran(0.3*game.intime))
4439         schedule(FSNOVA, expran(0.5*game.intime))
4440         # next snapshot will be sooner
4441         schedule(FSNAP, expran(0.25*game.state.remtime))
4442
4443         if game.state.nscrem:
4444             schedule(FSCMOVE, 0.2777)
4445         game.isatb = 0
4446         unschedule(FCDBAS)
4447         unschedule(FSCDBAS)
4448         game.battle.invalidate()
4449         # Make sure Galileo is consistant -- Snapshot may have been taken
4450         # when on planet, which would give us two Galileos!
4451         gotit = False
4452         for l in range(game.inplan):
4453             if game.state.planets[l].known == "shuttle_down":
4454                 gotit = True
4455                 if game.iscraft == "onship" and game.ship=='E':
4456                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4457                     game.iscraft = "offship"
4458         # Likewise, if in the original time the Galileo was abandoned, but
4459         # was on ship earlier, it would have vanished -- let's restore it.
4460         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4461             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4462             game.iscraft = "onship"
4463         # There used to be code to do the actual reconstrction here,
4464         # but the starchart is now part of the snapshotted galaxy state.
4465         prout(_("Spock has reconstructed a correct star chart from memory"))
4466     else:
4467         # Go forward in time
4468         game.optime = expran(0.5*game.intime)
4469         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4470         # cheat to make sure no tractor beams occur during time warp
4471         postpone(FTBEAM, game.optime)
4472         game.damage[DRADIO] += game.optime
4473     newqad()
4474     events()        # Stas Sergeev added this -- do pending events
4475
4476 def probe():
4477     "Launch deep-space probe."
4478     # New code to launch a deep space probe
4479     if game.nprobes == 0:
4480         scanner.chew()
4481         skip(1)
4482         if game.ship == 'E':
4483             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4484         else:
4485             prout(_("Ye Faerie Queene has no deep space probes."))
4486         return
4487     if damaged(DDSP):
4488         scanner.chew()
4489         skip(1)
4490         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4491         return
4492     if is_scheduled(FDSPROB):
4493         scanner.chew()
4494         skip(1)
4495         if damaged(DRADIO) and game.condition != "docked":
4496             prout(_("Spock-  \"Records show the previous probe has not yet"))
4497             prout(_("   reached its destination.\""))
4498         else:
4499             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4500         return
4501     key = scanner.nexttok()
4502     if key == "IHEOL":
4503         if game.nprobes == 1:
4504             prout(_("1 probe left."))
4505         else:
4506             prout(_("%d probes left") % game.nprobes)
4507         proutn(_("Are you sure you want to fire a probe? "))
4508         if not ja():
4509             return
4510     game.isarmed = False
4511     if key == "IHALPHA" and scanner.token == "armed":
4512         game.isarmed = True
4513         key = scanner.nexttok()
4514     elif key == "IHEOL":
4515         proutn(_("Arm NOVAMAX warhead? "))
4516         game.isarmed = ja()
4517     elif key == "IHREAL":                # first element of course
4518         scanner.push(scanner.token)
4519     try:
4520         game.probe = getcourse(isprobe=True)
4521     except TrekError:
4522         return
4523     game.nprobes -= 1
4524     schedule(FDSPROB, 0.01) # Time to move one sector
4525     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4526     game.ididit = True
4527     return
4528
4529 def mayday():
4530     "Yell for help from nearest starbase."
4531     # There's more than one way to move in this game!
4532     scanner.chew()
4533     # Test for conditions which prevent calling for help
4534     if game.condition == "docked":
4535         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4536         return
4537     if damaged(DRADIO):
4538         prout(_("Subspace radio damaged."))
4539         return
4540     if not game.state.baseq:
4541         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4542         return
4543     if game.landed:
4544         prout(_("You must be aboard the %s.") % crmshp())
4545         return
4546     # OK -- call for help from nearest starbase
4547     game.nhelp += 1
4548     if game.base.i!=0:
4549         # There's one in this quadrant
4550         ddist = (game.base - game.sector).distance()
4551     else:
4552         ibq = None      # Force base-quadrant game to persist past loop
4553         ddist = FOREVER
4554         for ibq in game.state.baseq:
4555             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4556             if xdist < ddist:
4557                 ddist = xdist
4558         if ibq is None:
4559             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4560             return
4561         # Since starbase not in quadrant, set up new quadrant
4562         game.quadrant = ibq
4563         newqad()
4564     # dematerialize starship
4565     game.quad[game.sector.i][game.sector.j]='.'
4566     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4567            % (game.quadrant, crmshp()))
4568     game.sector.invalidate()
4569     for m in range(1, 5+1):
4570         w = game.base.scatter()
4571         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4572             # found one -- finish up
4573             game.sector = w
4574             break
4575     if game.sector is None:
4576         prout(_("You have been lost in space..."))
4577         finish(FMATERIALIZE)
4578         return
4579     # Give starbase three chances to rematerialize starship
4580     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4581     for m in range(1, 3+1):
4582         if m == 1: proutn(_("1st"))
4583         elif m == 2: proutn(_("2nd"))
4584         elif m == 3: proutn(_("3rd"))
4585         proutn(_(" attempt to re-materialize ") + crmshp())
4586         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4587         textcolor(RED)
4588         warble()
4589         if rnd.real() > probf:
4590             break
4591         prout(_("fails."))
4592         textcolor(DEFAULT)
4593         if game.options & OPTION_CURSES:
4594             curses.delay_output(500)
4595     if m > 3:
4596         game.quad[game.sector.i][game.sector.j]='?'
4597         game.alive = False
4598         drawmaps(1)
4599         setwnd(message_window)
4600         finish(FMATERIALIZE)
4601         return
4602     game.quad[game.sector.i][game.sector.j]=game.ship
4603     textcolor(GREEN)
4604     prout(_("succeeds."))
4605     textcolor(DEFAULT)
4606     dock(False)
4607     skip(1)
4608     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4609
4610 def abandon():
4611     "Abandon ship."
4612     scanner.chew()
4613     if game.condition=="docked":
4614         if game.ship!='E':
4615             prout(_("You cannot abandon Ye Faerie Queene."))
4616             return
4617     else:
4618         # Must take shuttle craft to exit
4619         if game.damage[DSHUTTL]==-1:
4620             prout(_("Ye Faerie Queene has no shuttle craft."))
4621             return
4622         if game.damage[DSHUTTL]<0:
4623             prout(_("Shuttle craft now serving Big Macs."))
4624             return
4625         if game.damage[DSHUTTL]>0:
4626             prout(_("Shuttle craft damaged."))
4627             return
4628         if game.landed:
4629             prout(_("You must be aboard the ship."))
4630             return
4631         if game.iscraft != "onship":
4632             prout(_("Shuttle craft not currently available."))
4633             return
4634         # Emit abandon ship messages
4635         skip(1)
4636         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4637         skip(1)
4638         prouts(_("***ALL HANDS ABANDON SHIP!"))
4639         skip(2)
4640         prout(_("Captain and crew escape in shuttle craft."))
4641         if not game.state.baseq:
4642             # Oops! no place to go...
4643             finish(FABANDN)
4644             return
4645         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4646         # Dispose of crew
4647         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4648             prout(_("Remainder of ship's complement beam down"))
4649             prout(_("to nearest habitable planet."))
4650         elif q.planet is not None and not damaged(DTRANSP):
4651             prout(_("Remainder of ship's complement beam down to %s.") %
4652                   q.planet)
4653         else:
4654             prout(_("Entire crew of %d left to die in outer space.") %
4655                   game.state.crew)
4656             game.casual += game.state.crew
4657             game.abandoned += game.state.crew
4658         # If at least one base left, give 'em the Faerie Queene
4659         skip(1)
4660         game.icrystl = False # crystals are lost
4661         game.nprobes = 0 # No probes
4662         prout(_("You are captured by Klingons and released to"))
4663         prout(_("the Federation in a prisoner-of-war exchange."))
4664         nb = rnd.integer(len(game.state.baseq))
4665         # Set up quadrant and position FQ adjacient to base
4666         if not game.quadrant == game.state.baseq[nb]:
4667             game.quadrant = game.state.baseq[nb]
4668             game.sector.i = game.sector.j = 5
4669             newqad()
4670         while True:
4671             # position next to base by trial and error
4672             game.quad[game.sector.i][game.sector.j] = '.'
4673             l = QUADSIZE
4674             for l in range(QUADSIZE):
4675                 game.sector = game.base.scatter()
4676                 if game.sector.valid_sector() and \
4677                        game.quad[game.sector.i][game.sector.j] == '.':
4678                     break
4679             if l < QUADSIZE:
4680                 break # found a spot
4681             game.sector.i=QUADSIZE/2
4682             game.sector.j=QUADSIZE/2
4683             newqad()
4684     # Get new commission
4685     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4686     game.state.crew = FULLCREW
4687     prout(_("Starfleet puts you in command of another ship,"))
4688     prout(_("the Faerie Queene, which is antiquated but,"))
4689     prout(_("still useable."))
4690     if game.icrystl:
4691         prout(_("The dilithium crystals have been moved."))
4692     game.imine = False
4693     game.iscraft = "offship" # Galileo disappears
4694     # Resupply ship
4695     game.condition="docked"
4696     for l in range(NDEVICES):
4697         game.damage[l] = 0.0
4698     game.damage[DSHUTTL] = -1
4699     game.energy = game.inenrg = 3000.0
4700     game.shield = game.inshld = 1250.0
4701     game.torps = game.intorps = 6
4702     game.lsupres=game.inlsr=3.0
4703     game.shldup=False
4704     game.warpfac=5.0
4705     game.brigfree = game.brigcapacity = 300
4706     return
4707
4708 # Code from planets.c begins here.
4709
4710 def consumeTime():
4711     "Abort a lengthy operation if an event interrupts it."
4712     game.ididit = True
4713     events()
4714     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4715         return True
4716     return False
4717
4718 def survey():
4719     "Report on (uninhabited) planets in the galaxy."
4720     iknow = False
4721     skip(1)
4722     scanner.chew()
4723     prout(_("Spock-  \"Planet report follows, Captain.\""))
4724     skip(1)
4725     for i in range(game.inplan):
4726         if game.state.planets[i].pclass == "destroyed":
4727             continue
4728         if (game.state.planets[i].known != "unknown" \
4729             and not game.state.planets[i].inhabited) \
4730             or game.idebug:
4731             iknow = True
4732             if game.idebug and game.state.planets[i].known=="unknown":
4733                 proutn("(Unknown) ")
4734             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4735             proutn(_("   class "))
4736             proutn(game.state.planets[i].pclass)
4737             proutn("   ")
4738             if game.state.planets[i].crystals != "present":
4739                 proutn(_("no "))
4740             prout(_("dilithium crystals present."))
4741             if game.state.planets[i].known=="shuttle_down":
4742                 prout(_("    Shuttle Craft Galileo on surface."))
4743     if not iknow:
4744         prout(_("No information available."))
4745
4746 def orbit():
4747     "Enter standard orbit."
4748     skip(1)
4749     scanner.chew()
4750     if game.inorbit:
4751         prout(_("Already in standard orbit."))
4752         return
4753     if damaged(DWARPEN) and damaged(DIMPULS):
4754         prout(_("Both warp and impulse engines damaged."))
4755         return
4756     if game.plnet is None:
4757         prout("There is no planet in this sector.")
4758         return
4759     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4760         prout(crmshp() + _(" not adjacent to planet."))
4761         skip(1)
4762         return
4763     game.optime = rnd.real(0.02, 0.05)
4764     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4765     newcnd()
4766     if consumeTime():
4767         return
4768     game.height = rnd.real(1400, 8600)
4769     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4770     game.inorbit = True
4771     game.ididit = True
4772
4773 def sensor():
4774     "Examine planets in this quadrant."
4775     if damaged(DSRSENS):
4776         if game.options & OPTION_TTY:
4777             prout(_("Short range sensors damaged."))
4778         return
4779     if game.iplnet is None:
4780         if game.options & OPTION_TTY:
4781             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4782         return
4783     if game.iplnet.known == "unknown":
4784         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4785         skip(1)
4786         prout(_("         Planet at Sector %s is of class %s.") %
4787               (game.plnet, game.iplnet.pclass))
4788         if game.iplnet.known=="shuttle_down":
4789             prout(_("         Sensors show Galileo still on surface."))
4790         proutn(_("         Readings indicate"))
4791         if game.iplnet.crystals != "present":
4792             proutn(_(" no"))
4793         prout(_(" dilithium crystals present.\""))
4794         if game.iplnet.known == "unknown":
4795             game.iplnet.known = "known"
4796     elif game.iplnet.inhabited:
4797         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4798         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4799
4800 def beam():
4801     "Use the transporter."
4802     nrgneed = 0
4803     scanner.chew()
4804     skip(1)
4805     if damaged(DTRANSP):
4806         prout(_("Transporter damaged."))
4807         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4808             skip(1)
4809             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4810             if ja():
4811                 shuttle()
4812         return
4813     if not game.inorbit:
4814         prout(crmshp() + _(" not in standard orbit."))
4815         return
4816     if game.shldup:
4817         prout(_("Impossible to transport through shields."))
4818         return
4819     if game.iplnet.known=="unknown":
4820         prout(_("Spock-  \"Captain, we have no information on this planet"))
4821         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4822         prout(_("  you may not go down.\""))
4823         return
4824     if not game.landed and game.iplnet.crystals=="absent":
4825         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4826         prout(_("  exploring a planet with no dilithium crystals."))
4827         proutn(_("  Are you sure this is wise?\" "))
4828         if not ja():
4829             scanner.chew()
4830             return
4831     if not (game.options & OPTION_PLAIN):
4832         nrgneed = 50 * game.skill + game.height / 100.0
4833         if nrgneed > game.energy:
4834             prout(_("Engineering to bridge--"))
4835             prout(_("  Captain, we don't have enough energy for transportation."))
4836             return
4837         if not game.landed and nrgneed * 2 > game.energy:
4838             prout(_("Engineering to bridge--"))
4839             prout(_("  Captain, we have enough energy only to transport you down to"))
4840             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4841             if game.iplnet.known == "shuttle_down":
4842                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4843             proutn(_("  Are you sure this is wise?\" "))
4844             if not ja():
4845                 scanner.chew()
4846                 return
4847     if game.landed:
4848         # Coming from planet
4849         if game.iplnet.known=="shuttle_down":
4850             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4851             if ja():
4852                 scanner.chew()
4853                 return
4854             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4855         prout(_("Landing party assembled, ready to beam up."))
4856         skip(1)
4857         prout(_("Kirk whips out communicator..."))
4858         prouts(_("BEEP  BEEP  BEEP"))
4859         skip(2)
4860         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4861     else:
4862         # Going to planet
4863         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4864         skip(1)
4865         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4866         skip(1)
4867         prout(_("Kirk-  \"Energize.\""))
4868     game.ididit = True
4869     skip(1)
4870     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4871     skip(2)
4872     if not rnd.withprob(0.98):
4873         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4874         skip(2)
4875         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4876         finish(FLOST)
4877         return
4878     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4879     game.landed = not game.landed
4880     game.energy -= nrgneed
4881     skip(2)
4882     prout(_("Transport complete."))
4883     if game.landed and game.iplnet.known=="shuttle_down":
4884         prout(_("The shuttle craft Galileo is here!"))
4885     if not game.landed and game.imine:
4886         game.icrystl = True
4887         game.cryprob = 0.05
4888     game.imine = False
4889     return
4890
4891 def mine():
4892     "Strip-mine a world for dilithium."
4893     skip(1)
4894     scanner.chew()
4895     if not game.landed:
4896         prout(_("Mining party not on planet."))
4897         return
4898     if game.iplnet.crystals == "mined":
4899         prout(_("This planet has already been strip-mined for dilithium."))
4900         return
4901     elif game.iplnet.crystals == "absent":
4902         prout(_("No dilithium crystals on this planet."))
4903         return
4904     if game.imine:
4905         prout(_("You've already mined enough crystals for this trip."))
4906         return
4907     if game.icrystl and game.cryprob == 0.05:
4908         prout(_("With all those fresh crystals aboard the ") + crmshp())
4909         prout(_("there's no reason to mine more at this time."))
4910         return
4911     game.optime = rnd.real(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4912     if consumeTime():
4913         return
4914     prout(_("Mining operation complete."))
4915     game.iplnet.crystals = "mined"
4916     game.imine = game.ididit = True
4917
4918 def usecrystals():
4919     "Use dilithium crystals."
4920     game.ididit = False
4921     skip(1)
4922     scanner.chew()
4923     if not game.icrystl:
4924         prout(_("No dilithium crystals available."))
4925         return
4926     if game.energy >= 1000:
4927         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4928         prout(_("  except when Condition Yellow exists."))
4929         return
4930     prout(_("Spock- \"Captain, I must warn you that loading"))
4931     prout(_("  raw dilithium crystals into the ship's power"))
4932     prout(_("  system may risk a severe explosion."))
4933     proutn(_("  Are you sure this is wise?\" "))
4934     if not ja():
4935         scanner.chew()
4936         return
4937     skip(1)
4938     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4939     prout(_("  Mr. Spock and I will try it.\""))
4940     skip(1)
4941     prout(_("Spock-  \"Crystals in place, Sir."))
4942     prout(_("  Ready to activate circuit.\""))
4943     skip(1)
4944     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4945     skip(1)
4946     if rnd.withprob(game.cryprob):
4947         prouts(_("  \"Activating now! - - No good!  It's***"))
4948         skip(2)
4949         prouts(_("***RED ALERT!  RED A*L********************************"))
4950         skip(1)
4951         stars()
4952         prouts(_("******************   KA-BOOM!!!!   *******************"))
4953         skip(1)
4954         kaboom()
4955         return
4956     game.energy += rnd.real(5000.0, 5500.0)
4957     prouts(_("  \"Activating now! - - "))
4958     prout(_("The instruments"))
4959     prout(_("   are going crazy, but I think it's"))
4960     prout(_("   going to work!!  Congratulations, Sir!\""))
4961     game.cryprob *= 2.0
4962     game.ididit = True
4963
4964 def shuttle():
4965     "Use shuttlecraft for planetary jaunt."
4966     scanner.chew()
4967     skip(1)
4968     if damaged(DSHUTTL):
4969         if game.damage[DSHUTTL] == -1.0:
4970             if game.inorbit and game.iplnet.known == "shuttle_down":
4971                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4972             else:
4973                 prout(_("Ye Faerie Queene had no shuttle craft."))
4974         elif game.damage[DSHUTTL] > 0:
4975             prout(_("The Galileo is damaged."))
4976         else: # game.damage[DSHUTTL] < 0
4977             prout(_("Shuttle craft is now serving Big Macs."))
4978         return
4979     if not game.inorbit:
4980         prout(crmshp() + _(" not in standard orbit."))
4981         return
4982     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4983         prout(_("Shuttle craft not currently available."))
4984         return
4985     if not game.landed and game.iplnet.known=="shuttle_down":
4986         prout(_("You will have to beam down to retrieve the shuttle craft."))
4987         return
4988     if game.shldup or game.condition == "docked":
4989         prout(_("Shuttle craft cannot pass through shields."))
4990         return
4991     if game.iplnet.known=="unknown":
4992         prout(_("Spock-  \"Captain, we have no information on this planet"))
4993         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4994         prout(_("  you may not fly down.\""))
4995         return
4996     game.optime = 3.0e-5*game.height
4997     if game.optime >= 0.8*game.state.remtime:
4998         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4999         proutn(_("  a maneuver would require approximately %2d%% of our") % \
5000                int(100*game.optime/game.state.remtime))
5001         prout(_("remaining time."))
5002         proutn(_("Are you sure this is wise?\" "))
5003         if not ja():
5004             game.optime = 0.0
5005             return
5006     if game.landed:
5007         # Kirk on planet
5008         if game.iscraft == "onship":
5009             # Galileo on ship!
5010             if not damaged(DTRANSP):
5011                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
5012                 if ja():
5013                     beam()
5014                     return
5015                 proutn(_("Shuttle crew"))
5016             else:
5017                 proutn(_("Rescue party"))
5018             prout(_(" boards Galileo and swoops toward planet surface."))
5019             game.iscraft = "offship"
5020             skip(1)
5021             if consumeTime():
5022                 return
5023             game.iplnet.known="shuttle_down"
5024             prout(_("Trip complete."))
5025             return
5026         else:
5027             # Ready to go back to ship
5028             prout(_("You and your mining party board the"))
5029             prout(_("shuttle craft for the trip back to the Enterprise."))
5030             skip(1)
5031             prouts(_("The short hop begins . . ."))
5032             skip(1)
5033             game.iplnet.known="known"
5034             game.icraft = True
5035             skip(1)
5036             game.landed = False
5037             if consumeTime():
5038                 return
5039             game.iscraft = "onship"
5040             game.icraft = False
5041             if game.imine:
5042                 game.icrystl = True
5043                 game.cryprob = 0.05
5044             game.imine = False
5045             prout(_("Trip complete."))
5046             return
5047     else:
5048         # Kirk on ship and so is Galileo
5049         prout(_("Mining party assembles in the hangar deck,"))
5050         prout(_("ready to board the shuttle craft \"Galileo\"."))
5051         skip(1)
5052         prouts(_("The hangar doors open; the trip begins."))
5053         skip(1)
5054         game.icraft = True
5055         game.iscraft = "offship"
5056         if consumeTime():
5057             return
5058         game.iplnet.known = "shuttle_down"
5059         game.landed = True
5060         game.icraft = False
5061         prout(_("Trip complete."))
5062         return
5063
5064 def deathray():
5065     "Use the big zapper."
5066     game.ididit = False
5067     skip(1)
5068     scanner.chew()
5069     if game.ship != 'E':
5070         prout(_("Ye Faerie Queene has no death ray."))
5071         return
5072     if len(game.enemies)==0:
5073         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
5074         return
5075     if damaged(DDRAY):
5076         prout(_("Death Ray is damaged."))
5077         return
5078     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
5079     prout(_("  is highly unpredictible.  Considering the alternatives,"))
5080     proutn(_("  are you sure this is wise?\" "))
5081     if not ja():
5082         return
5083     prout(_("Spock-  \"Acknowledged.\""))
5084     skip(1)
5085     game.ididit = True
5086     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
5087     skip(1)
5088     prout(_("Crew scrambles in emergency preparation."))
5089     prout(_("Spock and Scotty ready the death ray and"))
5090     prout(_("prepare to channel all ship's power to the device."))
5091     skip(1)
5092     prout(_("Spock-  \"Preparations complete, sir.\""))
5093     prout(_("Kirk-  \"Engage!\""))
5094     skip(1)
5095     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5096     skip(1)
5097     dprob = 0.30
5098     if game.options & OPTION_PLAIN:
5099         dprob = 0.5
5100     r = rnd.real()
5101     if r > dprob:
5102         prouts(_("Sulu- \"Captain!  It's working!\""))
5103         skip(2)
5104         while len(game.enemies) > 0:
5105             deadkl(game.enemies[-1].location, game.quad[game.enemies[-1].location.i][game.enemies[-1].location.j],game.enemies[-1].location)
5106         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5107         if game.unwon() == 0:
5108             finish(FWON)
5109         if (game.options & OPTION_PLAIN) == 0:
5110             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5111             if rnd.withprob(0.05):
5112                 prout(_("   is still operational.\""))
5113             else:
5114                 prout(_("   has been rendered nonfunctional.\""))
5115                 game.damage[DDRAY] = 39.95
5116         return
5117     r = rnd.real()        # Pick failure method
5118     if r <= 0.30:
5119         prouts(_("Sulu- \"Captain!  It's working!\""))
5120         skip(1)
5121         prouts(_("***RED ALERT!  RED ALERT!"))
5122         skip(1)
5123         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5124         skip(1)
5125         prouts(_("***RED ALERT!  RED A*L********************************"))
5126         skip(1)
5127         stars()
5128         prouts(_("******************   KA-BOOM!!!!   *******************"))
5129         skip(1)
5130         kaboom()
5131         return
5132     if r <= 0.55:
5133         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5134         skip(1)
5135         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5136         skip(1)
5137         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5138         prout(_("  have apparently been transformed into strange mutations."))
5139         prout(_("  Vulcans do not seem to be affected."))
5140         skip(1)
5141         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5142         finish(FDRAY)
5143         return
5144     if r <= 0.75:
5145         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5146         skip(2)
5147         proutn(_("Spock-  \"I believe the word is"))
5148         prouts(_(" *ASTONISHING*"))
5149         prout(_(" Mr. Sulu."))
5150         for i in range(QUADSIZE):
5151             for j in range(QUADSIZE):
5152                 if game.quad[i][j] == '.':
5153                     game.quad[i][j] = '?'
5154         prout(_("  Captain, our quadrant is now infested with"))
5155         prouts(_(" - - - - - -  *THINGS*."))
5156         skip(1)
5157         prout(_("  I have no logical explanation.\""))
5158         return
5159     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5160     skip(1)
5161     prout(_("Scotty-  \"There are so many tribbles down here"))
5162     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5163     finish(FTRIBBLE)
5164     return
5165
5166 # Code from reports.c begins here
5167
5168 def attackreport(curt):
5169     "eport status of bases under attack."
5170     if not curt:
5171         if is_scheduled(FCDBAS):
5172             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5173             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5174         elif game.isatb == 1:
5175             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5176             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5177         else:
5178             prout(_("No Starbase is currently under attack."))
5179     else:
5180         if is_scheduled(FCDBAS):
5181             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5182         if game.isatb:
5183             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5184         clreol()
5185
5186 def report():
5187     # report on general game status
5188     scanner.chew()
5189     # pylint: disable=consider-using-ternary
5190     s1 = (game.thawed and _("thawed ")) or ""
5191     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5192     s3 = (None, _("novice"), _("fair"),
5193           _("good"), _("expert"), _("emeritus"))[game.skill]
5194     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5195     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5196         prout(_("No plaque is allowed."))
5197     if game.tourn:
5198         prout(_("This is tournament game %d.") % game.tourn)
5199     prout(_("Your secret password is \"%s\"") % game.passwd)
5200     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5201                                                       (game.inkling + game.incom + game.inscom)))
5202     if game.incom - len(game.state.kcmdr):
5203         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5204     elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
5205         prout(_(", but no Commanders."))
5206     else:
5207         prout(".")
5208     if game.skill > SKILL_FAIR:
5209         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5210     if len(game.state.baseq) != game.inbase:
5211         proutn(_("There "))
5212         if game.inbase-len(game.state.baseq)==1:
5213             proutn(_("has been 1 base"))
5214         else:
5215             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5216         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5217     else:
5218         prout(_("There are %d bases.") % game.inbase)
5219     if communicating() or game.iseenit:
5220         # Don't report this if not seen and
5221         # either the radio is dead or not at base!
5222         attackreport(False)
5223         game.iseenit = True
5224     if game.casual:
5225         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5226     if game.brigcapacity != game.brigfree:
5227         embriggened = game.brigcapacity-game.brigfree
5228         if embriggened == 1:
5229             prout(_("1 Klingon in brig"))
5230         else:
5231             prout(_("%d Klingons in brig.") %  embriggened)
5232         if game.kcaptured == 0:
5233             pass
5234         elif game.kcaptured == 1:
5235             prout(_("1 captured Klingon turned in to Starfleet."))
5236         else:
5237             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5238     if game.nhelp:
5239         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5240     if game.ship == 'E':
5241         proutn(_("You have "))
5242         if game.nprobes:
5243             proutn("%d" % (game.nprobes))
5244         else:
5245             proutn(_("no"))
5246         proutn(_(" deep space probe"))
5247         if game.nprobes!=1:
5248             proutn(_("s"))
5249         prout(".")
5250     if communicating() and is_scheduled(FDSPROB):
5251         if game.isarmed:
5252             proutn(_("An armed deep space probe is in "))
5253         else:
5254             proutn(_("A deep space probe is in "))
5255         prout("Quadrant %s." % game.probe.quadrant())
5256     if game.icrystl:
5257         if game.cryprob <= .05:
5258             prout(_("Dilithium crystals aboard ship... not yet used."))
5259         else:
5260             i=0
5261             ai = 0.05
5262             while game.cryprob > ai:
5263                 ai *= 2.0
5264                 i += 1
5265             prout(_("Dilithium crystals have been used %d time%s.") % \
5266                   (i, (_("s"), "")[i==1]))
5267     skip(1)
5268
5269 def lrscan(silent):
5270     "Long-range sensor scan."
5271     if damaged(DLRSENS):
5272         # Now allow base's sensors if docked
5273         if game.condition != "docked":
5274             if not silent:
5275                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5276             return
5277         if not silent:
5278             prout(_("Starbase's long-range scan"))
5279     elif not silent:
5280         prout(_("Long-range scan"))
5281     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5282         if not silent:
5283             proutn(" ")
5284         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5285             if not Coord(x, y).valid_quadrant():
5286                 if not silent:
5287                     proutn("  -1")
5288             else:
5289                 if not damaged(DRADIO):
5290                     game.state.galaxy[x][y].charted = True
5291                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5292                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5293                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5294                 if not silent and game.state.galaxy[x][y].supernova:
5295                     proutn(" ***")
5296                 elif not silent:
5297                     cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
5298                     proutn(((3 - len(cn)) * '.') + cn)
5299         if not silent:
5300             prout(" ")
5301
5302 def damagereport():
5303     "Damage report."
5304     jdam = False
5305     scanner.chew()
5306     for i in range(NDEVICES):
5307         if damaged(i):
5308             if not jdam:
5309                 prout(_("DEVICE                             REPAIR TIMES"))
5310                 prout(_("                                   IN FLIGHT                DOCKED"))
5311                 jdam = True
5312             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5313                                                game.damage[i]+0.05,
5314                                                DOCKFAC*game.damage[i]+0.005))
5315     if not jdam:
5316         prout(_("All devices functional."))
5317
5318 def rechart():
5319     "Update the chart in the Enterprise's computer from galaxy data."
5320     game.lastchart = game.state.date
5321     for i in range(GALSIZE):
5322         for j in range(GALSIZE):
5323             if game.state.galaxy[i][j].charted:
5324                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5325                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5326                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5327
5328 def chart():
5329     "Display the star chart."
5330     scanner.chew()
5331     if (game.options & OPTION_AUTOSCAN):
5332         lrscan(silent=True)
5333     if communicating():
5334         rechart()
5335     if game.lastchart < game.state.date and game.condition == "docked":
5336         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5337         rechart()
5338     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5339     if game.state.date > game.lastchart:
5340         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5341     prout("      1    2    3    4    5    6    7    8")
5342     for i in range(GALSIZE):
5343         if (game.options & OPTION_ALPHAMERIC):
5344             proutn("%c |" % letterize(i+1))
5345         else:
5346             proutn("%d |" % (i+1))
5347         for j in range(GALSIZE):
5348             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5349                 proutn("<")
5350             else:
5351                 proutn(" ")
5352             if game.state.galaxy[i][j].supernova:
5353                 show = "***"
5354             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5355                 show = ".1."
5356             elif game.state.galaxy[i][j].charted:
5357                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5358                 if (game.options & OPTION_DOTFILL):
5359                     show = show.replace(" ", ".")
5360             else:
5361                 show = "..."
5362             proutn(show)
5363             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5364                 proutn(">")
5365             else:
5366                 proutn(" ")
5367         proutn("  |")
5368         if i<GALSIZE:
5369             skip(1)
5370
5371 def sectscan(goodScan, i, j):
5372     "Light up an individual dot in a sector."
5373     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5374         if game.quad[i][j] in ('E', 'F'):
5375             if game.iscloaked:
5376                 highvideo()
5377             textcolor({"green":GREEN,
5378                        "yellow":YELLOW,
5379                        "red":RED,
5380                        "docked":CYAN,
5381                        "dead":BROWN}[game.condition])
5382         else:
5383             textcolor({'?':LIGHTMAGENTA,
5384                        'K':LIGHTRED,
5385                        'S':LIGHTRED,
5386                        'C':LIGHTRED,
5387                        'R':LIGHTRED,
5388                        'T':LIGHTRED,
5389                        '@':LIGHTGREEN,
5390                        'P':LIGHTGREEN,
5391                       }.get(game.quad[i][j], DEFAULT))
5392         proutn("%c " % game.quad[i][j])
5393         textcolor(DEFAULT)
5394     else:
5395         proutn("- ")
5396
5397 def status(req=0):
5398     "Emit status report lines"
5399     if not req or req == 1:
5400         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5401                % (game.state.date, game.state.remtime))
5402     if not req or req == 2:
5403         if game.condition != "docked":
5404             newcnd()
5405         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5406                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5407         if game.iscloaked:
5408             prout(_(", CLOAKED"))
5409     if not req or req == 3:
5410         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5411     if not req or req == 4:
5412         if damaged(DLIFSUP):
5413             if game.condition == "docked":
5414                 s = _("DAMAGED, Base provides")
5415             else:
5416                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5417         else:
5418             s = _("ACTIVE")
5419         prstat(_("Life Support"), s)
5420     if not req or req == 5:
5421         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5422     if not req or req == 6:
5423         extra = ""
5424         if game.icrystl and (game.options & OPTION_SHOWME):
5425             extra = _(" (have crystals)")
5426         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5427     if not req or req == 7:
5428         prstat(_("Torpedoes"), "%d" % (game.torps))
5429     if not req or req == 8:
5430         if damaged(DSHIELD):
5431             s = _("DAMAGED,")
5432         elif game.shldup:
5433             s = _("UP,")
5434         else:
5435             s = _("DOWN,")
5436         data = _(" %d%% %.1f units") \
5437                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5438         prstat(_("Shields"), s+data)
5439     if not req or req == 9:
5440         prstat(_("Klingons Left"), "%d" % game.unwon())
5441     if not req or req == 10:
5442         if game.options & OPTION_WORLDS:
5443             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5444             if plnet and plnet.inhabited:
5445                 prstat(_("Major system"), plnet.name)
5446             else:
5447                 prout(_("Sector is uninhabited"))
5448     elif not req or req == 11:
5449         attackreport(not req)
5450
5451 def request():
5452     "Request specified status data, a historical relic from slow TTYs."
5453     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5454     while scanner.nexttok() == "IHEOL":
5455         proutn(_("Information desired? "))
5456     scanner.chew()
5457     if scanner.token in requests:
5458         status(requests.index(scanner.token))
5459     else:
5460         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5461         prout(("  date, condition, position, lsupport, warpfactor,"))
5462         prout(("  energy, torpedoes, shields, klingons, system, time."))
5463
5464 def srscan():
5465     "Short-range scan."
5466     goodScan=True
5467     if damaged(DSRSENS):
5468         # Allow base's sensors if docked
5469         if game.condition != "docked":
5470             prout(_("   S.R. SENSORS DAMAGED!"))
5471             goodScan=False
5472         else:
5473             prout(_("  [Using Base's sensors]"))
5474     else:
5475         prout(_("     Short-range scan"))
5476     if goodScan and communicating():
5477         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5478         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5479         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5480         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5481     prout("    1 2 3 4 5 6 7 8 9 10")
5482     if game.condition != "docked":
5483         newcnd()
5484     for i in range(QUADSIZE):
5485         if (game.options & OPTION_ALPHAMERIC):
5486             proutn("%c   " % letterize(i+1))
5487         else:
5488             proutn("%2d  " % (i+1))
5489         for j in range(QUADSIZE):
5490             sectscan(goodScan, i, j)
5491         skip(1)
5492
5493 def eta():
5494     "Use computer to get estimated time of arrival for a warp jump."
5495     w1 = Coord(); w2 = Coord()
5496     prompt = False
5497     if damaged(DCOMPTR):
5498         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5499         skip(1)
5500         return
5501     if scanner.nexttok() != "IHREAL":
5502         prompt = True
5503         scanner.chew()
5504         proutn(_("Destination quadrant and/or sector? "))
5505         if scanner.nexttok()!="IHREAL":
5506             huh()
5507             return
5508     w1.j = int(scanner.real-0.5)
5509     if scanner.nexttok() != "IHREAL":
5510         huh()
5511         return
5512     w1.i = int(scanner.real-0.5)
5513     if scanner.nexttok() == "IHREAL":
5514         w2.j = int(scanner.real-0.5)
5515         if scanner.nexttok() != "IHREAL":
5516             huh()
5517             return
5518         w2.i = int(scanner.real-0.5)
5519     else:
5520         if game.quadrant.j>w1.i:
5521             w2.i = 0
5522         else:
5523             w2.i=QUADSIZE-1
5524         if game.quadrant.i>w1.j:
5525             w2.j = 0
5526         else:
5527             w2.j=QUADSIZE-1
5528     if not w1.valid_quadrant() or not w2.valid_sector():
5529         huh()
5530         return
5531     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5532                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5533     wfl = False
5534     if prompt:
5535         prout(_("Answer \"no\" if you don't know the value:"))
5536     while True:
5537         scanner.chew()
5538         proutn(_("Time or arrival date? "))
5539         if scanner.nexttok()=="IHREAL":
5540             ttime = scanner.real
5541             if ttime > game.state.date:
5542                 ttime -= game.state.date # Actually a star date
5543             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5544             if ttime <= 1e-10 or twarp > 10:
5545                 prout(_("We'll never make it, sir."))
5546                 scanner.chew()
5547                 return
5548             twarp = max(twarp, 1.0)
5549             break
5550         scanner.chew()
5551         proutn(_("Warp factor? "))
5552         if scanner.nexttok()== "IHREAL":
5553             wfl = True
5554             twarp = scanner.real
5555             if twarp<1.0 or twarp > 10.0:
5556                 huh()
5557                 return
5558             break
5559         prout(_("Captain, certainly you can give me one of these."))
5560     while True:
5561         scanner.chew()
5562         ttime = (10.0*dist)/twarp**2
5563         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5564         if tpower >= game.energy:
5565             prout(_("Insufficient energy, sir."))
5566             if not game.shldup or tpower > game.energy*2.0:
5567                 if not wfl:
5568                     return
5569                 proutn(_("New warp factor to try? "))
5570                 if scanner.nexttok() == "IHREAL":
5571                     wfl = True
5572                     twarp = scanner.real
5573                     if twarp<1.0 or twarp > 10.0:
5574                         huh()
5575                         return
5576                     continue
5577                 else:
5578                     scanner.chew()
5579                     skip(1)
5580                     return
5581             prout(_("But if you lower your shields,"))
5582             proutn(_("remaining"))
5583             tpower /= 2
5584         else:
5585             proutn(_("Remaining"))
5586         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5587         if wfl:
5588             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5589         elif twarp==1.0:
5590             prout(_("Any warp speed is adequate."))
5591         else:
5592             prout(_("Minimum warp needed is %.2f,") % (twarp))
5593             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5594         if game.state.remtime < ttime:
5595             prout(_("Unfortunately, the Federation will be destroyed by then."))
5596         if twarp > 6.0:
5597             prout(_("You'll be taking risks at that speed, Captain"))
5598         if (game.isatb==1 and game.state.kscmdr == w1 and \
5599              scheduled(FSCDBAS)< ttime+game.state.date) or \
5600             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5601             prout(_("The starbase there will be destroyed by then."))
5602         proutn(_("New warp factor to try? "))
5603         if scanner.nexttok() == "IHREAL":
5604             wfl = True
5605             twarp = scanner.real
5606             if twarp<1.0 or twarp > 10.0:
5607                 huh()
5608                 return
5609         else:
5610             scanner.chew()
5611             skip(1)
5612             return
5613
5614 # This is new in SST2K.
5615
5616 def goptions():
5617     mode = scanner.nexttok()
5618     if mode == "IHEOL":
5619         active = []
5620         for k, v in option_names.items():
5621             if (v & game.options) and k != "ALL":
5622                 active.append(k)
5623         active.sort()
5624         prout(str(" ".join(active)))
5625     elif scanner.token in {"set", "clear"}:
5626         mode = scanner.token
5627         changemask = 0
5628         while True:
5629             scanner.nexttok()
5630             if scanner.type == "IHEOL":
5631                 break
5632             if scanner.token.upper() in option_names:
5633                 changemask |= option_names[scanner.token.upper()]
5634             else:
5635                 prout(_("No such option as ") + scanner.token)
5636         if mode == "set":
5637             if (not (game.options & OPTION_CURSES)) and (changemask & OPTION_CURSES):
5638                 iostart()
5639             game.options |= changemask
5640         elif mode == "clear":
5641             if (game.options & OPTION_CURSES) and (not (changemask & OPTION_CURSES)):
5642                 ioend()
5643             game.options &=~ changemask
5644         prout(_("Acknowledged, Captain."))
5645     else:
5646         huh()
5647     scanner.chew()
5648     skip(1)
5649
5650 # Code from setup.c begins here
5651
5652 def prelim():
5653     "Issue a historically correct banner."
5654     skip(2)
5655     prout(_("-SUPER- STAR TREK"))
5656     skip(1)
5657 # From the FORTRAN original
5658 #    prout(_("Latest update-21 Sept 78"))
5659 #    skip(1)
5660
5661 def freeze(boss):
5662     "Save game."
5663     if boss:
5664         scanner.push("emsave.trk")
5665     key = scanner.nexttok()
5666     if key == "IHEOL":
5667         proutn(_("File name: "))
5668         key = scanner.nexttok()
5669     if key != "IHALPHA":
5670         huh()
5671         return
5672     if '.' not in scanner.token:
5673         scanner.token += ".trk"
5674     try:
5675         fp = open(scanner.token, "wb")
5676     except IOError:
5677         prout(_("Can't freeze game as file %s") % scanner.token)
5678         return
5679     pickle.dump(game, fp)
5680     fp.close()
5681     scanner.chew()
5682
5683 def thaw():
5684     "Retrieve saved game."
5685     global game
5686     game.passwd = None
5687     key = scanner.nexttok()
5688     if key == "IHEOL":
5689         proutn(_("File name: "))
5690         key = scanner.nexttok()
5691     if key != "IHALPHA":
5692         huh()
5693         return True
5694     if '.' not in scanner.token:
5695         scanner.token += ".trk"
5696     try:
5697         fp = open(scanner.token, "rb")
5698     except IOError:
5699         prout(_("Can't thaw game in %s") % scanner.token)
5700         return True
5701     game = pickle.load(fp)
5702     fp.close()
5703     scanner.chew()
5704     return False
5705
5706 # I used <http://www.memory-alpha.org> to find planets
5707 # with references in ST:TOS.  Earth and the Alpha Centauri
5708 # Colony have been omitted.
5709 #
5710 # Some planets marked Class G and P here will be displayed as class M
5711 # because of the way planets are generated. This is a known bug.
5712 systnames = (
5713     # Federation Worlds
5714     _("Andoria (Fesoan)"),        # several episodes
5715     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5716     _("Vulcan (T'Khasi)"),        # many episodes
5717     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5718     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5719     _("Ardana"),                # TOS: "The Cloud Minders"
5720     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5721     _("Gideon"),                # TOS: "The Mark of Gideon"
5722     _("Aldebaran III"),                # TOS: "The Deadly Years"
5723     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5724     _("Altair IV"),                # TOS: "Amok Time
5725     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5726     _("Benecia"),                # TOS: "The Conscience of the King"
5727     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5728     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5729     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5730     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5731     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5732     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5733     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5734     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5735     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5736     _("Janus IV"),                # TOS: "The Devil in the Dark"
5737     _("Makus III"),                # TOS: "The Galileo Seven"
5738     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5739     _("Omega IV"),                # TOS: "The Omega Glory"
5740     _("Regulus V"),                # TOS: "Amok Time
5741     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5742     # Worlds from BSD Trek
5743     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5744     _("Beta III"),                # TOS: "The Return of the Archons"
5745     _("Triacus"),                # TOS: "And the Children Shall Lead",
5746     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5747     #        # Others
5748     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5749     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5750     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5751     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5752     #    _("Tiburon"),                # TOS: "The Way to Eden"
5753     #    _("Merak II"),                # TOS: "The Cloud Minders"
5754     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5755     #    _("Iotia"),                # TOS: "A Piece of the Action"
5756 )
5757
5758 device = (
5759     _("S. R. Sensors"), \
5760     _("L. R. Sensors"), \
5761     _("Phasers"), \
5762     _("Photon Tubes"), \
5763     _("Life Support"), \
5764     _("Warp Engines"), \
5765     _("Impulse Engines"), \
5766     _("Shields"), \
5767     _("Subspace Radio"), \
5768     _("Shuttle Craft"), \
5769     _("Computer"), \
5770     _("Navigation System"), \
5771     _("Transporter"), \
5772     _("Shield Control"), \
5773     _("Death Ray"), \
5774     _("D. S. Probe"), \
5775     _("Cloaking Device"), \
5776 )
5777
5778 def setup():
5779     "Prepare to play, set up cosmos."
5780     w = Coord()
5781     #  Decide how many of everything
5782     if choose():
5783         return # frozen game
5784     # Prepare the Enterprise
5785     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5786     game.ship = 'E'
5787     game.state.crew = FULLCREW
5788     game.energy = game.inenrg = 5000.0
5789     game.shield = game.inshld = 2500.0
5790     game.inlsr = 4.0
5791     game.lsupres = 4.0
5792     game.quadrant = randplace(GALSIZE)
5793     game.sector = randplace(QUADSIZE)
5794     game.torps = game.intorps = 10
5795     game.nprobes = rnd.integer(2, 5)
5796     game.warpfac = 5.0
5797     for i in range(NDEVICES):
5798         game.damage[i] = 0.0
5799     # Set up assorted game parameters
5800     game.battle = Coord()
5801     game.state.date = game.indate = 100.0 * rnd.real(20, 51)
5802     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5803     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5804     game.isatb = game.state.nplankl = 0
5805     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5806     game.iscraft = "onship"
5807     game.landed = False
5808     game.alive = True
5809
5810     # the galaxy
5811     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5812     # the starchart
5813     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5814
5815     game.state.planets = []      # Planet information
5816     game.state.baseq = []      # Base quadrant coordinates
5817     game.state.kcmdr = []      # Commander quadrant coordinates
5818     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5819
5820     # Starchart is functional but we've never seen it
5821     game.lastchart = FOREVER
5822     # Put stars in the galaxy
5823     game.instar = 0
5824     for i in range(GALSIZE):
5825         for j in range(GALSIZE):
5826             # Can't have more stars per quadrant than fit in one decimal digit,
5827             # if we do the chart representation will break.
5828             k = rnd.integer(1, min(10, QUADSIZE**2/10))
5829             game.instar += k
5830             game.state.galaxy[i][j].stars = k
5831     # Locate star bases in galaxy
5832     if game.idebug:
5833         prout("=== Allocating %d bases" % game.inbase)
5834     for i in range(game.inbase):
5835         while True:
5836             while True:
5837                 w = randplace(GALSIZE)
5838                 if not game.state.galaxy[w.i][w.j].starbase:
5839                     break
5840             contflag = False
5841             # C version: for (j = i-1; j > 0; j--)
5842             # so it did them in the opposite order.
5843             for j in range(1, i):
5844                 # Improved placement algorithm to spread out bases
5845                 distq = (w - game.state.baseq[j]).distance()
5846                 if distq < 6.0*(BASEMAX+1-game.inbase) and rnd.withprob(0.75):
5847                     contflag = True
5848                     if game.idebug:
5849                         prout("=== Abandoning base #%d at %s" % (i, w))
5850                     break
5851                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5852                     if game.idebug:
5853                         prout("=== Saving base #%d, close to #%d" % (i, j))
5854             if not contflag:
5855                 break
5856         if game.idebug:
5857             prout("=== Placing base #%d in quadrant %s" % (i, w))
5858         game.state.baseq.append(w)
5859         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5860     # Position ordinary Klingon Battle Cruisers
5861     krem = game.inkling
5862     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5863     klumper = min(klumper, MAXKLQUAD)
5864     while True:
5865         r = rnd.real()
5866         klump = int((1.0 - r*r)*klumper)
5867         klump = min(klump, krem)
5868         krem -= klump
5869         while True:
5870             w = randplace(GALSIZE)
5871             if not game.state.galaxy[w.i][w.j].supernova and \
5872                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5873                 break
5874         game.state.galaxy[w.i][w.j].klingons += klump
5875         if krem <= 0:
5876             break
5877     # Position Klingon Commander Ships
5878     for i in range(game.incom):
5879         while True:
5880             w = randplace(GALSIZE)
5881             if not welcoming(w) or w in game.state.kcmdr:
5882                 continue
5883             if (game.state.galaxy[w.i][w.j].klingons or rnd.withprob(0.25)):
5884                 break
5885         game.state.galaxy[w.i][w.j].klingons += 1
5886         game.state.kcmdr.append(w)
5887     # Locate planets in galaxy
5888     for i in range(game.inplan):
5889         while True:
5890             w = randplace(GALSIZE)
5891             if game.state.galaxy[w.i][w.j].planet is None:
5892                 break
5893         new = Planet()
5894         new.quadrant = w
5895         new.crystals = "absent"
5896         if (game.options & OPTION_WORLDS) and i < NINHAB:
5897             new.pclass = "M"        # All inhabited planets are class M
5898             new.crystals = "absent"
5899             new.known = "known"
5900             new.name = systnames[i]
5901             new.inhabited = True
5902         else:
5903             new.pclass = ("M", "N", "O")[rnd.integer(0, 3)]
5904             if rnd.withprob(0.33):
5905                 new.crystals = "present"
5906             new.known = "unknown"
5907             new.inhabited = False
5908         game.state.galaxy[w.i][w.j].planet = new
5909         game.state.planets.append(new)
5910     # Locate Romulans
5911     for i in range(game.state.nromrem):
5912         w = randplace(GALSIZE)
5913         game.state.galaxy[w.i][w.j].romulans += 1
5914     # Place the Super-Commander if needed
5915     if game.state.nscrem > 0:
5916         while True:
5917             w = randplace(GALSIZE)
5918             if welcoming(w):
5919                 break
5920         game.state.kscmdr = w
5921         game.state.galaxy[w.i][w.j].klingons += 1
5922     # Initialize times for extraneous events
5923     schedule(FSNOVA, expran(0.5 * game.intime))
5924     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5925     schedule(FSNAP, rnd.real(1.0, 2.0)) # Force an early snapshot
5926     schedule(FBATTAK, expran(0.3*game.intime))
5927     unschedule(FCDBAS)
5928     if game.state.nscrem:
5929         schedule(FSCMOVE, 0.2777)
5930     else:
5931         unschedule(FSCMOVE)
5932     unschedule(FSCDBAS)
5933     unschedule(FDSPROB)
5934     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5935         schedule(FDISTR, expran(1.0 + game.intime))
5936     else:
5937         unschedule(FDISTR)
5938     unschedule(FENSLV)
5939     unschedule(FREPRO)
5940     # Place thing (in tournament game, we don't want one!)
5941     # New in SST2K: never place the Thing near a starbase.
5942     # This makes sense and avoids a special case in the old code.
5943     global thing
5944     if game.tourn is None:
5945         while True:
5946             thing = randplace(GALSIZE)
5947             if thing not in game.state.baseq:
5948                 break
5949     skip(2)
5950     game.state.snap = False
5951     if game.skill == SKILL_NOVICE:
5952         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5953         prout(_("a deadly Klingon invasion force. As captain of the United"))
5954         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5955         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5956         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5957         prout(_("your mission.  As you proceed you may be given more time."))
5958         skip(1)
5959         prout(_("You will have %d supporting starbases.") % (game.inbase))
5960         proutn(_("Starbase locations-  "))
5961     else:
5962         prout(_("Stardate %d.") % int(game.state.date))
5963         skip(1)
5964         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5965         prout(_("An unknown number of Romulans."))
5966         if game.state.nscrem:
5967             prout(_("And one (GULP) Super-Commander."))
5968         prout(_("%d stardates.") % int(game.intime))
5969         proutn(_("%d starbases in ") % game.inbase)
5970     for i in range(game.inbase):
5971         proutn(repr(game.state.baseq[i]))
5972         proutn("  ")
5973     skip(2)
5974     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5975     proutn(_(" Sector %s") % game.sector)
5976     skip(2)
5977     prout(_("Good Luck!"))
5978     if game.state.nscrem:
5979         prout(_("  YOU'LL NEED IT."))
5980     waitfor()
5981     clrscr()
5982     setwnd(message_window)
5983     newqad()
5984     if len(game.enemies) - (thing == game.quadrant) - (game.tholian is not None):
5985         game.shldup = True
5986     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5987         attack(torps_ok=False)
5988
5989 def choose():
5990     "Choose your game type."
5991     while True:
5992         game.tourn = game.length = 0
5993         game.thawed = False
5994         game.skill = SKILL_NONE
5995         # Do not chew here, we want to use command-line tokens
5996         if not scanner.inqueue: # Can start with command line options
5997             proutn(_("Would you like a regular, tournament, or saved game? "))
5998         scanner.nexttok()
5999         if scanner.sees("tournament"):
6000             while scanner.nexttok() == "IHEOL":
6001                 proutn(_("Type in tournament number-"))
6002             if scanner.real == 0:
6003                 scanner.chew()
6004                 continue # We don't want a blank entry
6005             game.tourn = int(round(scanner.real))
6006             rnd.seed(scanner.real)
6007             if logfp:
6008                 logfp.write("# rnd.seed(%d)\n" % scanner.real)
6009             break
6010         if scanner.sees("saved") or scanner.sees("frozen"):
6011             if thaw():
6012                 continue
6013             scanner.chew()
6014             if game.passwd is None:
6015                 continue
6016             if not game.alldone:
6017                 game.thawed = True # No plaque if not finished
6018             report()
6019             waitfor()
6020             return True
6021         if scanner.sees("regular"):
6022             break
6023         proutn(_("What is \"%s\"? ") % scanner.token)
6024         scanner.chew()
6025     while game.length==0 or game.skill==SKILL_NONE:
6026         if scanner.nexttok() == "IHALPHA":
6027             if scanner.sees("short"):
6028                 game.length = 1
6029             elif scanner.sees("medium"):
6030                 game.length = 2
6031             elif scanner.sees("long"):
6032                 game.length = 4
6033             elif scanner.sees("novice"):
6034                 game.skill = SKILL_NOVICE
6035             elif scanner.sees("fair"):
6036                 game.skill = SKILL_FAIR
6037             elif scanner.sees("good"):
6038                 game.skill = SKILL_GOOD
6039             elif scanner.sees("expert"):
6040                 game.skill = SKILL_EXPERT
6041             elif scanner.sees("emeritus"):
6042                 game.skill = SKILL_EMERITUS
6043             else:
6044                 proutn(_("What is \""))
6045                 proutn(scanner.token)
6046                 prout("\"?")
6047         else:
6048             scanner.chew()
6049             if game.length==0:
6050                 proutn(_("Would you like a Short, Medium, or Long game? "))
6051             elif game.skill == SKILL_NONE:
6052                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
6053     # Choose game options -- added by ESR for SST2K
6054     if scanner.nexttok() != "IHALPHA":
6055         scanner.chew()
6056         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
6057         scanner.nexttok()
6058     if scanner.sees("plain"):
6059         # Approximates the UT FORTRAN version.
6060         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)
6061         game.options |= OPTION_PLAIN
6062     elif scanner.sees("almy"):
6063         # Approximates Tom Almy's version.
6064         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_DOTFILL | OPTION_ALPHAMERIC)
6065         game.options |= OPTION_ALMY
6066     elif scanner.sees("fancy") or scanner.sees("\n"):
6067         pass
6068     elif len(scanner.token):
6069         proutn(_("What is \"%s\"?") % scanner.token)
6070     setpassword()
6071     if game.passwd == "debug":                # pragma: no cover
6072         game.idebug = True
6073         prout("=== Debug mode enabled.")
6074     # Use parameters to generate initial values of things
6075     game.damfac = 0.5 * game.skill
6076     game.inbase = rnd.integer(BASEMIN, BASEMAX+1)
6077     game.inplan = 0
6078     if game.options & OPTION_PLANETS:
6079         game.inplan += rnd.integer(MAXUNINHAB/2, MAXUNINHAB+1)
6080     if game.options & OPTION_WORLDS:
6081         game.inplan += int(NINHAB)
6082     game.state.nromrem = game.inrom = rnd.integer(2 * game.skill)
6083     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
6084     game.state.remtime = 7.0 * game.length
6085     game.intime = game.state.remtime
6086     game.inkling = int(2.0*game.intime*((game.skill+1 - 2*rnd.real())*game.skill*0.1+.15))
6087     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*rnd.real()))
6088     game.state.remres = (game.inkling+4*game.incom)*game.intime
6089     game.inresor = game.state.remres
6090     if game.inkling > 50:
6091         game.inbase += 1
6092     return False
6093
6094 def dropin(iquad=None):
6095     "Drop a feature on a random dot in the current quadrant."
6096     while True:
6097         w = randplace(QUADSIZE)
6098         if game.quad[w.i][w.j] == '.':
6099             break
6100     if iquad is not None:
6101         game.quad[w.i][w.j] = iquad
6102     return w
6103
6104 def newcnd():
6105     "Update our alert status."
6106     game.condition = "green"
6107     if game.energy < 1000.0:
6108         game.condition = "yellow"
6109     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
6110         game.condition = "red"
6111     if not game.alive:
6112         game.condition="dead"
6113
6114 def newkling():
6115     "Drop new Klingon into current quadrant."
6116     return Enemy('K', loc=dropin(), power=rnd.real(300,450)+25.0*game.skill)
6117
6118 def sortenemies():
6119     "Sort enemies by distance so 'nearest' is meaningful."
6120     game.enemies.sort(key=lambda x: x.kdist)
6121
6122 def newqad():
6123     "Set up a new state of quadrant, for when we enter or re-enter it."
6124     game.justin = True
6125     game.iplnet = None
6126     game.neutz = game.inorbit = game.landed = False
6127     game.ientesc = game.iseenit = game.isviolreported = False
6128     game.tholian = None
6129     # Create a blank quadrant
6130     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6131     if game.iscate:
6132         # Attempt to escape Super-commander, so tbeam back!
6133         game.iscate = False
6134         game.ientesc = True
6135     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6136     # cope with supernova
6137     if q.supernova:
6138         return
6139     game.klhere = q.klingons
6140     game.irhere = q.romulans
6141     # Position Starship
6142     game.quad[game.sector.i][game.sector.j] = game.ship
6143     game.enemies = []
6144     if q.klingons:
6145         # Position ordinary Klingons
6146         for _i in range(game.klhere):
6147             newkling()
6148         # If we need a commander, promote a Klingon
6149         for cmdr in game.state.kcmdr:
6150             if cmdr == game.quadrant:
6151                 e = game.enemies[game.klhere-1]
6152                 game.quad[e.location.i][e.location.j] = 'C'
6153                 e.power = rnd.real(950,1350) + 50.0*game.skill
6154                 break
6155         # If we need a super-commander, promote a Klingon
6156         if game.quadrant == game.state.kscmdr:
6157             e = game.enemies[0]
6158             game.quad[e.location.i][e.location.j] = 'S'
6159             e.power = rnd.real(1175.0,  1575.0) + 125.0*game.skill
6160             game.iscate = (game.remkl() > 1)
6161     # Put in Romulans if needed
6162     for _i in range(q.romulans):
6163         Enemy('R', loc=dropin(), power=rnd.real(400.0,850.0)+50.0*game.skill)
6164     # If quadrant needs a starbase, put it in
6165     if q.starbase:
6166         game.base = dropin('B')
6167     # If quadrant needs a planet, put it in
6168     if q.planet:
6169         game.iplnet = q.planet
6170         if not q.planet.inhabited:
6171             game.plnet = dropin('P')
6172         else:
6173             game.plnet = dropin('@')
6174     # Check for condition
6175     newcnd()
6176     # Check for RNZ
6177     if game.irhere > 0 and game.klhere == 0:
6178         game.neutz = True
6179         if not damaged(DRADIO):
6180             skip(1)
6181             prout(_("LT. Uhura- \"Captain, an urgent message."))
6182             prout(_("  I'll put it on audio.\"  CLICK"))
6183             skip(1)
6184             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6185             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6186     # Put in THING if needed
6187     if thing == game.quadrant:
6188         Enemy(etype='?', loc=dropin(),
6189               power=rnd.real(6000,6500.0)+250.0*game.skill)
6190         if not damaged(DSRSENS):
6191             skip(1)
6192             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6193             prout(_("    Please examine your short-range scan.\""))
6194     # Decide if quadrant needs a Tholian; lighten up if skill is low
6195     if game.options & OPTION_THOLIAN:
6196         if (game.skill < SKILL_GOOD and rnd.withprob(0.02)) or \
6197             (game.skill == SKILL_GOOD and rnd.withprob(0.05)) or \
6198             (game.skill > SKILL_GOOD and rnd.withprob(0.08)):
6199             w = Coord()
6200             while True:
6201                 w.i = rnd.withprob(0.5) * (QUADSIZE-1)
6202                 w.j = rnd.withprob(0.5) * (QUADSIZE-1)
6203                 if game.quad[w.i][w.j] == '.':
6204                     break
6205             game.tholian = Enemy(etype='T', loc=w,
6206                                  power=rnd.integer(100, 500) + 25.0*game.skill)
6207             # Reserve unoccupied corners
6208             if game.quad[0][0]=='.':
6209                 game.quad[0][0] = 'X'
6210             if game.quad[0][QUADSIZE-1]=='.':
6211                 game.quad[0][QUADSIZE-1] = 'X'
6212             if game.quad[QUADSIZE-1][0]=='.':
6213                 game.quad[QUADSIZE-1][0] = 'X'
6214             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6215                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6216     sortenemies()
6217     # And finally the stars
6218     for _i in range(q.stars):
6219         dropin('*')
6220     # Put in a few black holes
6221     for _i in range(1, 3+1):
6222         if rnd.withprob(0.5):
6223             dropin(' ')
6224     # Take out X's in corners if Tholian present
6225     if game.tholian:
6226         if game.quad[0][0]=='X':
6227             game.quad[0][0] = '.'
6228         if game.quad[0][QUADSIZE-1]=='X':
6229             game.quad[0][QUADSIZE-1] = '.'
6230         if game.quad[QUADSIZE-1][0]=='X':
6231             game.quad[QUADSIZE-1][0] = '.'
6232         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6233             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6234     # This should guarantee that replay games don't lose info about the chart
6235     if (game.options & OPTION_AUTOSCAN) or replayfp:
6236         lrscan(silent=True)
6237
6238 def setpassword():
6239     "Set the self-destruct password."
6240     if game.options & OPTION_PLAIN:
6241         while True:
6242             scanner.chew()
6243             proutn(_("Please type in a secret password- "))
6244             scanner.nexttok()
6245             game.passwd = scanner.token
6246             #game.passwd = getpass.getpass("Please type in a secret password- ")
6247             if game.passwd is not None:
6248                 break
6249     else:
6250         game.passwd = ""
6251         game.passwd += chr(ord('a')+rnd.integer(26))
6252         game.passwd += chr(ord('a')+rnd.integer(26))
6253         game.passwd += chr(ord('a')+rnd.integer(26))
6254
6255 # Code from sst.c begins here
6256
6257 commands = [
6258     ("SRSCAN",           OPTION_TTY),
6259     ("STATUS",           OPTION_TTY),
6260     ("REQUEST",          OPTION_TTY),
6261     ("LRSCAN",           OPTION_TTY),
6262     ("PHASERS",          0),
6263     ("TORPEDO",          0),
6264     ("PHOTONS",          0),
6265     ("MOVE",             0),
6266     ("SHIELDS",          0),
6267     ("DOCK",             0),
6268     ("DAMAGES",          0),
6269     ("CHART",            0),
6270     ("IMPULSE",          0),
6271     ("REST",             0),
6272     ("WARP",             0),
6273     ("SENSORS",          OPTION_PLANETS),
6274     ("ORBIT",            OPTION_PLANETS),
6275     ("TRANSPORT",        OPTION_PLANETS),
6276     ("MINE",             OPTION_PLANETS),
6277     ("CRYSTALS",         OPTION_PLANETS),
6278     ("SHUTTLE",          OPTION_PLANETS),
6279     ("PLANETS",          OPTION_PLANETS),
6280     ("REPORT",           0),
6281     ("COMPUTER",         0),
6282     ("COMMANDS",         0),
6283     ("EMEXIT",           0),
6284     ("PROBE",            OPTION_PROBE),
6285     ("SAVE",             0),
6286     ("FREEZE",           0),        # Synonym for SAVE
6287     ("OPTIONS",          0),
6288     ("ABANDON",          0),
6289     # No abbreviations accepted after this point
6290     ("DESTRUCT",         0),
6291     ("DEATHRAY",         0),
6292     ("CAPTURE",          OPTION_CAPTURE),
6293     ("CLOAK",            OPTION_CLOAK),
6294     ("DEBUG",            0),
6295     ("MAYDAY",           0),
6296     ("SOS",              0),        # Synonym for MAYDAY
6297     ("CALL",             0),        # Synonym for MAYDAY
6298     ("QUIT",             0),
6299     ("HELP",             0),
6300     ("SCORE",            0),
6301     ("CURSES",           0),
6302     ("",                 0),
6303 ]
6304
6305 def listCommands():                # pragma: no cover
6306     "Generate a list of legal commands."
6307     # Coverage-disabled because testing for this is fragile
6308     # in the presence of changes in the command set.
6309     prout(_("LEGAL COMMANDS ARE:"))
6310     emitted = 0
6311     for (key, opt) in commands:
6312         if not opt or (opt & game.options):
6313             proutn("%-12s " % key)
6314             emitted += 1
6315             if emitted % 5 == 4:
6316                 skip(1)
6317     skip(1)
6318
6319 def helpme():
6320     "Browse on-line help."
6321     key = scanner.nexttok()
6322     while True:
6323         if key == "IHEOL":
6324             setwnd(prompt_window)
6325             proutn(_("Help on what command? "))
6326             key = scanner.nexttok()
6327         setwnd(message_window)
6328         if key == "IHEOL":
6329             return
6330         cmds = [x[0] for x in commands]
6331         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6332             break
6333         skip(1)
6334         listCommands()
6335         key = "IHEOL"
6336         scanner.chew()
6337         skip(1)
6338     cmd = scanner.token.upper()
6339     for directory in docpath:
6340         try:
6341             fp = open(os.path.join(directory, "sst.doc"), "r")
6342             break
6343         except IOError:
6344             pass
6345     else:
6346         prout(_("Spock-  \"Captain, that information is missing from the"))
6347         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6348         proutn(_("   in these directories: %s") % ":".join(docpath))
6349         prout(".\"")
6350         # This used to continue: "You need to find SST.DOC and put
6351         # it in the current directory."
6352         return
6353     while True:
6354         linebuf = fp.readline()
6355         if linebuf == '':
6356             prout(_("Spock- \"Captain, there is no information on that command.\""))
6357             fp.close()
6358             return
6359         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6360             linebuf = linebuf[3:].strip()
6361             if cmd.upper() == linebuf:
6362                 break
6363     skip(1)
6364     prout(_("Spock- \"Captain, I've found the following information:\""))
6365     skip(1)
6366     while True:
6367         linebuf = fp.readline()
6368         if "******" in linebuf:
6369             break
6370         proutn(linebuf)
6371     fp.close()
6372
6373 def makemoves():
6374     "Command-interpretation loop."
6375     def checkviol():
6376         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6377             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6378             game.ncviol += 1
6379             game.isviolreported = True
6380     while True:         # command loop
6381         drawmaps(1)
6382         while True:        # get a command
6383             hitme = False
6384             game.optime = game.justin = False
6385             scanner.chew()
6386             setwnd(prompt_window)
6387             clrscr()
6388             proutn("COMMAND> ")
6389             if scanner.nexttok() == "IHEOL":
6390                 if game.options & OPTION_CURSES:
6391                     makechart()
6392                 continue
6393             elif scanner.token == "":
6394                 continue
6395             game.ididit = False
6396             clrscr()
6397             setwnd(message_window)
6398             clrscr()
6399             abandon_passed = False
6400             cmd = ""    # Force cmd to persist after loop
6401             opt = 0     # Force opt to persist after loop
6402             for (cmd, opt) in commands:
6403                 # commands after ABANDON cannot be abbreviated
6404                 if cmd == "ABANDON":
6405                     abandon_passed = True
6406                 if cmd == scanner.token.upper() or (not abandon_passed \
6407                         and cmd.startswith(scanner.token.upper())):
6408                     break
6409             if cmd == "":                # pragma: no cover
6410                 listCommands()
6411                 continue
6412             elif opt and not (opt & game.options):
6413                 huh()
6414             else:
6415                 break
6416         if game.options & OPTION_CURSES:
6417             prout("COMMAND> %s" % cmd)
6418         if cmd == "SRSCAN":                # srscan
6419             srscan()
6420         elif cmd == "STATUS":                # status
6421             status()
6422         elif cmd == "REQUEST":                # status request
6423             request()
6424         elif cmd == "LRSCAN":                # long range scan
6425             lrscan(silent=False)
6426         elif cmd == "PHASERS":                # phasers
6427             phasers()
6428             if game.ididit:
6429                 checkviol()
6430                 hitme = True
6431         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6432             torps()
6433             if game.ididit:
6434                 checkviol()
6435                 hitme = True
6436         elif cmd == "MOVE":                # move under warp
6437             warp(wcourse=None, involuntary=False)
6438         elif cmd == "SHIELDS":                # shields
6439             doshield(shraise=False)
6440             if game.ididit:
6441                 hitme = True
6442                 game.shldchg = False
6443         elif cmd == "DOCK":                # dock at starbase
6444             dock(True)
6445             if game.ididit:
6446                 attack(torps_ok=False)
6447         elif cmd == "DAMAGES":                # damage reports
6448             damagereport()
6449         elif cmd == "CHART":                # chart
6450             makechart()
6451         elif cmd == "IMPULSE":                # impulse
6452             impulse()
6453         elif cmd == "REST":                # rest
6454             wait()
6455             if game.ididit:
6456                 hitme = True
6457         elif cmd == "WARP":                # warp
6458             setwarp()
6459         elif cmd == "SENSORS":                # sensors
6460             sensor()
6461         elif cmd == "ORBIT":                # orbit
6462             orbit()
6463             if game.ididit:
6464                 hitme = True
6465         elif cmd == "TRANSPORT":                # transport "beam"
6466             beam()
6467         elif cmd == "MINE":                # mine
6468             mine()
6469             if game.ididit:
6470                 hitme = True
6471         elif cmd == "CRYSTALS":                # crystals
6472             usecrystals()
6473             if game.ididit:
6474                 hitme = True
6475         elif cmd == "SHUTTLE":                # shuttle
6476             shuttle()
6477             if game.ididit:
6478                 hitme = True
6479         elif cmd == "PLANETS":                # Planet list
6480             survey()
6481         elif cmd == "REPORT":                # Game Report
6482             report()
6483         elif cmd == "COMPUTER":                # use COMPUTER!
6484             eta()
6485         elif cmd == "COMMANDS":
6486             listCommands()
6487         elif cmd == "EMEXIT":                # Emergency exit
6488             clrscr()                        # Hide screen
6489             freeze(True)                # forced save
6490             raise SystemExit(1)                # And quick exit
6491         elif cmd == "PROBE":
6492             probe()                        # Launch probe
6493             if game.ididit:
6494                 hitme = True
6495         elif cmd == "ABANDON":                # Abandon Ship
6496             abandon()
6497         elif cmd == "DESTRUCT":                # Self Destruct
6498             selfdestruct()
6499         elif cmd == "SAVE":                # Save Game
6500             freeze(False)
6501             clrscr()
6502             if game.skill > SKILL_GOOD:
6503                 prout(_("WARNING--Saved games produce no plaques!"))
6504         elif cmd == "DEATHRAY":                # Try a desparation measure
6505             deathray()
6506             if game.ididit:
6507                 hitme = True
6508         elif cmd == "CAPTURE":
6509             capture()
6510         elif cmd == "CLOAK":
6511             cloak()
6512         elif cmd == "DEBUGCMD":                # What do we want for debug???
6513             debugme()
6514         elif cmd == "MAYDAY":                # Call for help
6515             mayday()
6516             if game.ididit:
6517                 hitme = True
6518         elif cmd == "QUIT":
6519             game.alldone = True                # quit the game
6520         elif cmd == "HELP":
6521             helpme()                        # get help
6522         elif cmd == "SCORE":
6523             score()                         # see current score
6524         elif cmd == "CURSES":
6525             game.options |= (OPTION_CURSES | OPTION_COLOR)
6526             iostart()
6527         elif cmd == "OPTIONS":
6528             goptions()
6529         while True:
6530             if game.alldone:
6531                 break                # Game has ended
6532             if game.optime != 0.0:
6533                 events()
6534                 if game.alldone:
6535                     break        # Events did us in
6536             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6537                 atover(False)
6538                 continue
6539             if hitme and not game.justin:
6540                 attack(torps_ok=True)
6541                 if game.alldone:
6542                     break
6543                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6544                     atover(False)
6545                     hitme = True
6546                     continue
6547             break
6548         if game.alldone:
6549             break
6550     if game.idebug:
6551         prout("=== Ending")
6552
6553 def cramen(ch):
6554     "Emit the name of an enemy or feature."
6555     if   ch == 'R': s = _("Romulan")
6556     elif ch == 'K': s = _("Klingon")
6557     elif ch == 'C': s = _("Commander")
6558     elif ch == 'S': s = _("Super-commander")
6559     elif ch == '*': s = _("Star")
6560     elif ch == 'P': s = _("Planet")
6561     elif ch == 'B': s = _("Starbase")
6562     elif ch == ' ': s = _("Black hole")
6563     elif ch == 'T': s = _("Tholian")
6564     elif ch == '#': s = _("Tholian web")
6565     elif ch == '?': s = _("Stranger")
6566     elif ch == '@': s = _("Inhabited World")
6567     else: s = "Unknown??"                # pragma: no cover
6568     return s
6569
6570 def crmena(loud, enemy, loctype, w):
6571     "Emit the name of an enemy and his location."
6572     buf = ""
6573     if loud:
6574         buf += "***"
6575     buf += cramen(enemy) + _(" at ")
6576     if loctype == "quadrant":
6577         buf += _("Quadrant ")
6578     elif loctype == "sector":
6579         buf += _("Sector ")
6580     return buf + repr(w)
6581
6582 def crmshp():
6583     "Emit our ship name."
6584     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6585
6586 def stars():
6587     "Emit a line of stars"
6588     prouts("******************************************************")
6589     skip(1)
6590
6591 def expran(avrage):
6592     return -avrage*math.log(1e-7 + rnd.real())
6593
6594 def randplace(size):
6595     "Choose a random location."
6596     w = Coord()
6597     w.i = rnd.integer(size)
6598     w.j = rnd.integer(size)
6599     return w
6600
6601 class sstscanner:
6602     def __init__(self):
6603         self.type = None
6604         self.token = None
6605         self.real = 0.0
6606         self.inqueue = []
6607     def nexttok(self):
6608         # Get a token from the user
6609         self.real = 0.0
6610         self.token = ''
6611         # Fill the token queue if nothing here
6612         while not self.inqueue:
6613             sline = cgetline()
6614             if curwnd==prompt_window:
6615                 clrscr()
6616                 setwnd(message_window)
6617                 clrscr()
6618             if sline == '':
6619                 return None
6620             if not sline:
6621                 continue
6622             else:
6623                 self.inqueue = sline.lstrip().split() + ["\n"]
6624         # From here on in it's all looking at the queue
6625         self.token = self.inqueue.pop(0)
6626         if self.token == "\n":
6627             self.type = "IHEOL"
6628             return "IHEOL"
6629         try:
6630             self.real = float(self.token)
6631             self.type = "IHREAL"
6632             return "IHREAL"
6633         except ValueError:
6634             pass
6635         # Treat as alpha
6636         self.token = self.token.lower()
6637         self.type = "IHALPHA"
6638         self.real = None
6639         return "IHALPHA"
6640     def append(self, tok):
6641         self.inqueue.append(tok)
6642     def push(self, tok):
6643         self.inqueue.insert(0, tok)
6644     def waiting(self):
6645         return self.inqueue
6646     def chew(self):
6647         # Demand input for next scan
6648         self.inqueue = []
6649         self.real = self.token = None
6650     def sees(self, s):
6651         # compares s to item and returns true if it matches to the length of s
6652         return s.startswith(self.token)
6653     def int(self):
6654         # Round token value to nearest integer
6655         return int(round(self.real))
6656     def getcoord(self):
6657         s = Coord()
6658         self.nexttok()
6659         if (game.options & OPTION_ALPHAMERIC):
6660             try:
6661                 if (self.type == "IHALPHA") and (self.token[0] in "abcdefghij") and (self.token[1] in "0123456789"):
6662                     s.i = ord(self.token[0]) - ord("a")
6663                     s.j = int(self.token[1:])-1
6664                     return s
6665             except (TypeError, IndexError):
6666                 huh()
6667                 return None
6668         if self.type != "IHREAL":
6669             huh()
6670             return None
6671         s.i = self.int()-1
6672         self.nexttok()
6673         if self.type != "IHREAL":
6674             huh()
6675             return None
6676         s.j = self.int()-1
6677         return s
6678     def __repr__(self):                # pragma: no cover
6679         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6680
6681 def ja():
6682     "Yes-or-no confirmation."
6683     scanner.chew()
6684     while True:
6685         scanner.nexttok()
6686         if scanner.token == 'y':
6687             return True
6688         if scanner.token == 'n':
6689             return False
6690         scanner.chew()
6691         proutn(_("Please answer with \"y\" or \"n\": "))
6692
6693 def huh():
6694     "Complain about unparseable input."
6695     scanner.chew()
6696     skip(1)
6697     prout(_("Beg your pardon, Captain?"))
6698
6699 def debugme():                # pragma: no cover
6700     "Access to the internals for debugging."
6701     proutn("Reset levels? ")
6702     if ja():
6703         if game.energy < game.inenrg:
6704             game.energy = game.inenrg
6705         game.shield = game.inshld
6706         game.torps = game.intorps
6707         game.lsupres = game.inlsr
6708     proutn("Reset damage? ")
6709     if ja():
6710         for i in range(NDEVICES):
6711             if game.damage[i] > 0.0:
6712                 game.damage[i] = 0.0
6713     proutn("Toggle debug flag? ")
6714     if ja():
6715         game.idebug = not game.idebug
6716         if game.idebug:
6717             prout("Debug output ON")
6718         else:
6719             prout("Debug output OFF")
6720     proutn("Cause selective damage? ")
6721     if ja():
6722         for i in range(NDEVICES):
6723             proutn("Kill %s?" % device[i])
6724             scanner.chew()
6725             key = scanner.nexttok()
6726             if key == "IHALPHA" and scanner.sees("y"):
6727                 game.damage[i] = 10.0
6728     proutn("Examine/change events? ")
6729     if ja():
6730         ev = Event()
6731         w = Coord()
6732         legends = {
6733             FSNOVA:  "Supernova       ",
6734             FTBEAM:  "T Beam          ",
6735             FSNAP:   "Snapshot        ",
6736             FBATTAK: "Base Attack     ",
6737             FCDBAS:  "Base Destroy    ",
6738             FSCMOVE: "SC Move         ",
6739             FSCDBAS: "SC Base Destroy ",
6740             FDSPROB: "Probe Move      ",
6741             FDISTR:  "Distress Call   ",
6742             FENSLV:  "Enslavement     ",
6743             FREPRO:  "Klingon Build   ",
6744         }
6745         for i in range(1, NEVENTS):
6746             proutn(legends[i])
6747             if is_scheduled(i):
6748                 proutn("%.2f" % (scheduled(i)-game.state.date))
6749                 if i in {FENSLV, FREPRO}:
6750                     ev = findevent(i)
6751                     proutn(" in %s" % ev.quadrant)
6752             else:
6753                 proutn("never")
6754             proutn("? ")
6755             scanner.chew()
6756             key = scanner.nexttok()
6757             if key == 'n':
6758                 unschedule(i)
6759                 scanner.chew()
6760             elif key == "IHREAL":
6761                 ev = schedule(i, scanner.real)
6762                 if i in {FENSLV, FREPRO}:
6763                     scanner.chew()
6764                     proutn("In quadrant- ")
6765                     key = scanner.nexttok()
6766                     # "IHEOL" says to leave coordinates as they are
6767                     if key != "IHEOL":
6768                         if key != "IHREAL":
6769                             prout("Event %d canceled, no x coordinate." % (i))
6770                             unschedule(i)
6771                             continue
6772                         w.i = int(round(scanner.real))
6773                         key = scanner.nexttok()
6774                         if key != "IHREAL":
6775                             prout("Event %d canceled, no y coordinate." % (i))
6776                             unschedule(i)
6777                             continue
6778                         w.j = int(round(scanner.real))
6779                         ev.quadrant = w
6780         scanner.chew()
6781     proutn("Induce supernova here? ")
6782     if ja():
6783         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6784         atover(True)
6785
6786 if __name__ == '__main__':
6787     try:
6788         #global line, thing, game
6789         game = None
6790         thing = Thingy()
6791         game = Gamestate()
6792         rnd = randomizer()
6793         logfp = None
6794         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6795         if os.getenv("TERM"):
6796             game.options |= OPTION_CURSES
6797         else:
6798             game.options |= OPTION_TTY
6799         seed = int(time.time())
6800         (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
6801         for (switch, val) in options:
6802             if switch == '-r':
6803                 # pylint: disable=raise-missing-from
6804                 try:
6805                     replayfp = open(val, "r")
6806                 except IOError:
6807                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6808                     raise SystemExit(1)
6809                 # pylint: disable=raise-missing-from
6810                 try:
6811                     line = replayfp.readline().strip()
6812                     (leader, __, seed) = line.split()
6813                     # pylint: disable=eval-used
6814                     seed = eval(seed)
6815                     line = replayfp.readline().strip()
6816                     arguments += line.split()[2:]
6817                 except ValueError:                # pragma: no cover
6818                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6819                     raise SystemExit(1)
6820                 game.options |= OPTION_TTY
6821                 game.options &=~ OPTION_CURSES
6822             elif switch == '-s':                # pragma: no cover
6823                 seed = int(val)
6824             elif switch == '-t':
6825                 game.options |= OPTION_TTY
6826                 game.options &=~ OPTION_CURSES
6827             elif switch == '-x':                # pragma: no cover
6828                 game.idebug = True
6829             elif switch == '-c':        # Enable curses debugging - undocumented
6830                 game.cdebug = True
6831             elif switch == '-V':                # pragma: no cover
6832                 print("SST2K", version)
6833                 raise SystemExit(0)
6834             else:                # pragma: no cover
6835                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6836                 raise SystemExit(1)
6837         # where to save the input in case of bugs
6838         if "TMPDIR" in os.environ:                # pragma: no cover
6839             tmpdir = os.environ['TMPDIR']
6840         else:
6841             tmpdir = "/tmp"
6842         try:
6843             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6844         except IOError:                # pragma: no cover
6845             sys.stderr.write("sst: warning, can't open logfile\n")
6846             sys.exit(1)
6847         if logfp:
6848             logfp.write("# seed %s\n" % seed)
6849             logfp.write("# arguments %s\n" % " ".join(arguments))
6850             logfp.write("# SST2K version %s\n" % version)
6851             logfp.write("# recorded by %s@%s on %s\n" % \
6852                     (getpass.getuser(),socket.getfqdn(),time.ctime()))
6853         rnd.seed(seed)
6854         scanner = sstscanner()
6855         for arg in arguments:
6856             scanner.append(arg)
6857         try:
6858             iostart()
6859             while True: # Play a game
6860                 setwnd(fullscreen_window)
6861                 clrscr()
6862                 prelim()
6863                 setup()
6864                 if game.alldone:
6865                     score()
6866                     game.alldone = False
6867                 else:
6868                     makemoves()
6869                 if replayfp:
6870                     break
6871                 skip(1)
6872                 if (game.options & OPTION_TTY):
6873                     stars()
6874                 skip(1)
6875                 if game.tourn and game.alldone:
6876                     proutn(_("Do you want your score recorded?"))
6877                     if ja():
6878                         scanner.chew()
6879                         scanner.push("\n")
6880                         freeze(False)
6881                 scanner.chew()
6882                 if (game.options & OPTION_TTY):
6883                     proutn(_("Do you want to play again? "))
6884                     if not ja():
6885                         break
6886                 else:
6887                     break
6888             skip(1)
6889             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6890         finally:
6891             ioend()
6892         raise SystemExit(0)
6893     except KeyboardInterrupt:                # pragma: no cover
6894         if logfp:
6895             logfp.close()
6896         print("")
6897
6898 # End.