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