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