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