Ready to ship 2.8
[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
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 >= 0 and self.i < GALSIZE and self.j >= 0 and self.j < GALSIZE
146     def valid_sector(self):
147         return 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 False
548     if enemy.type == 'R':
549         return False # 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     if nsteps > QUADSIZE:
681         nsteps = QUADSIZE # This shouldn't be necessary
682     if nsteps < 1:
683         nsteps = 1 # This shouldn't be necessary
684     if game.idebug:
685         proutn("NSTEPS = %d:" % nsteps)
686     # Compute preferred values of delta X and Y
687     m = game.sector - enemy.location
688     if 2.0 * abs(m.i) < abs(m.j):
689         m.i = 0
690     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
691         m.j = 0
692     m = (motion * m).sgn()
693     goto = enemy.location
694     # main move loop
695     for ll in range(nsteps):
696         if game.idebug:
697             proutn(" %d" % (ll+1))
698         # Check if preferred position available
699         look = goto + m
700         if m.i < 0:
701             krawli = 1
702         else:
703             krawli = -1
704         if m.j < 0:
705             krawlj = 1
706         else:
707             krawlj = -1
708         success = False
709         attempts = 0 # Settle mysterious hang problem
710         while attempts < 20 and not success:
711             attempts += 1
712             if look.i < 0 or look.i >= QUADSIZE:
713                 if motion < 0:
714                     return tryexit(enemy, look, irun)
715                 if krawli == m.i or m.j == 0:
716                     break
717                 look.i = goto.i + krawli
718                 krawli = -krawli
719             elif look.j < 0 or look.j >= QUADSIZE:
720                 if motion < 0:
721                     return tryexit(enemy, look, irun)
722                 if krawlj == m.j or m.i == 0:
723                     break
724                 look.j = goto.j + krawlj
725                 krawlj = -krawlj
726             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
727                 # See if enemy should ram ship
728                 if game.quad[look.i][look.j] == game.ship and \
729                     (enemy.type == 'C' or enemy.type == 'S'):
730                     collision(rammed=True, enemy=enemy)
731                     return []
732                 if krawli != m.i and m.j != 0:
733                     look.i = goto.i + krawli
734                     krawli = -krawli
735                 elif krawlj != m.j and m.i != 0:
736                     look.j = goto.j + krawlj
737                     krawlj = -krawlj
738                 else:
739                     break # we have failed
740             else:
741                 success = True
742         if success:
743             goto = look
744             if game.idebug:
745                 proutn(repr(goto))
746         else:
747             break # done early
748     if game.idebug:
749         skip(1)
750     # Enemy moved, but is still in sector
751     return [(False, enemy, old_dist, goto)]
752
753 def moveklings():
754     "Sequence Klingon tactical movement."
755     if game.idebug:
756         prout("== MOVCOM")
757     # Figure out which Klingon is the commander (or Supercommander)
758     # and do move
759     tacmoves = []
760     if game.quadrant in game.state.kcmdr:
761         for enemy in game.enemies:
762             if enemy.type == 'C':
763                 tacmoves += movebaddy(enemy)
764     if game.state.kscmdr == game.quadrant:
765         for enemy in game.enemies:
766             if enemy.type == 'S':
767                 tacmoves += movebaddy(enemy)
768                 break
769     # If skill level is high, move other Klingons and Romulans too!
770     # Move these last so they can base their actions on what the
771     # commander(s) do.
772     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
773         for enemy in game.enemies:
774             if enemy.type in ('K', 'R'):
775                 tacmoves += movebaddy(enemy)
776     return tacmoves
777
778 def movescom(iq, avoid):
779     "Supercommander movement helper."
780     # Avoid quadrants with bases if we want to avoid Enterprise
781     if not welcoming(iq) or (avoid and iq in game.state.baseq):
782         return False
783     if game.justin and not game.iscate:
784         return False
785     # do the move
786     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
787     game.state.kscmdr = iq
788     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
789     if game.state.kscmdr == game.quadrant:
790         # SC has scooted, remove him from current quadrant
791         game.iscate = False
792         game.isatb = 0
793         game.ientesc = False
794         unschedule(FSCDBAS)
795         for enemy in game.enemies:
796             if enemy.type == 'S':
797                 enemy.move(None)
798         game.klhere -= 1
799         if game.condition != "docked":
800             newcnd()
801         sortenemies()
802     # check for a helpful planet
803     for i in range(game.inplan):
804         if game.state.planets[i].quadrant == game.state.kscmdr and \
805             game.state.planets[i].crystals == "present":
806             # destroy the planet
807             game.state.planets[i].pclass = "destroyed"
808             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
809             if communicating():
810                 announce()
811                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
812                 prout(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
813                 prout(_("   by the Super-commander.\""))
814             break
815     return True # looks good!
816
817 def supercommander():
818     "Move the Super Commander."
819     iq = Coord()
820     sc = Coord()
821     ibq = Coord()
822     idelta = Coord()
823     basetbl = []
824     if game.idebug:
825         prout("== SUPERCOMMANDER")
826     # Decide on being active or passive
827     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 \
828             (game.state.date-game.indate) < 3.0)
829     if not game.iscate and avoid:
830         # compute move away from Enterprise
831         idelta = game.state.kscmdr-game.quadrant
832         if idelta.distance() > 2.0:
833             # circulate in space
834             idelta.i = game.state.kscmdr.j-game.quadrant.j
835             idelta.j = game.quadrant.i-game.state.kscmdr.i
836     else:
837         # compute distances to starbases
838         if not game.state.baseq:
839             # nothing left to do
840             unschedule(FSCMOVE)
841             return
842         sc = game.state.kscmdr
843         for (i, base) in enumerate(game.state.baseq):
844             basetbl.append((i, (base - sc).distance()))
845         if len(game.state.baseq) > 1:
846             basetbl.sort(key=lambda x: x[1])
847         # look for nearest base without a commander, no Enterprise, and
848         # without too many Klingons, and not already under attack.
849         ifindit = iwhichb = 0
850         for (i2, base) in enumerate(game.state.baseq):
851             i = basetbl[i2][0]        # bug in original had it not finding nearest
852             if base == game.quadrant or base == game.battle or not welcoming(base):
853                 continue
854             # if there is a commander, and no other base is appropriate,
855             # we will take the one with the commander
856             for cmdr in game.state.kcmdr:
857                 if base == cmdr and ifindit != 2:
858                     ifindit = 2
859                     iwhichb = i
860                     break
861             else:        # no commander -- use this one
862                 ifindit = 1
863                 iwhichb = i
864                 break
865         if ifindit == 0:
866             return # Nothing suitable -- wait until next time
867         ibq = game.state.baseq[iwhichb]
868         # decide how to move toward base
869         idelta = ibq - game.state.kscmdr
870     # Maximum movement is 1 quadrant in either or both axes
871     idelta = idelta.sgn()
872     # try moving in both x and y directions
873     # there was what looked like a bug in the Almy C code here,
874     # but it might be this translation is just wrong.
875     iq = game.state.kscmdr + idelta
876     if not movescom(iq, avoid):
877         # failed -- try some other maneuvers
878         if idelta.i == 0 or idelta.j == 0:
879             # attempt angle move
880             if idelta.i != 0:
881                 iq.j = game.state.kscmdr.j + 1
882                 if not movescom(iq, avoid):
883                     iq.j = game.state.kscmdr.j - 1
884                     movescom(iq, avoid)
885             elif idelta.j != 0:
886                 iq.i = game.state.kscmdr.i + 1
887                 if not movescom(iq, avoid):
888                     iq.i = game.state.kscmdr.i - 1
889                     movescom(iq, avoid)
890         else:
891             # try moving just in x or y
892             iq.j = game.state.kscmdr.j
893             if not movescom(iq, avoid):
894                 iq.j = game.state.kscmdr.j + idelta.j
895                 iq.i = game.state.kscmdr.i
896                 movescom(iq, avoid)
897     # check for a base
898     if len(game.state.baseq) == 0:
899         unschedule(FSCMOVE)
900     else:
901         for ibq in game.state.baseq:
902             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
903                 # attack the base
904                 if avoid:
905                     return # no, don't attack base!
906                 game.iseenit = False
907                 game.isatb = 1
908                 schedule(FSCDBAS, rnd.real(1.0, 3.0))
909                 if is_scheduled(FCDBAS):
910                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
911                 if not communicating():
912                     return # no warning
913                 game.iseenit = True
914                 announce()
915                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
916                       % game.state.kscmdr)
917                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
918                 prout(_("   It can survive until stardate %d.\"") \
919                        % int(scheduled(FSCDBAS)))
920                 if not game.resting:
921                     return
922                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
923                 if not ja():
924                     return
925                 game.resting = False
926                 game.optime = 0.0 # actually finished
927                 return
928     # Check for intelligence report
929     if not game.idebug and \
930         (rnd.withprob(0.8) or \
931          (not communicating()) or \
932          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
933         return
934     announce()
935     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
936     prout(_("   the Super-commander is in Quadrant %s.") % game.state.kscmdr)
937     return
938
939 def movetholian():
940     "Move the Tholian."
941     if not game.tholian or game.justin:
942         return
943     tid = Coord()
944     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
945         tid.i = 0
946         tid.j = QUADSIZE-1
947     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
948         tid.i = QUADSIZE-1
949         tid.j = QUADSIZE-1
950     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
951         tid.i = QUADSIZE-1
952         tid.j = 0
953     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
954         tid.i = 0
955         tid.j = 0
956     else:
957         # something is wrong!
958         game.tholian.move(None)
959         prout("***Internal error: Tholian in a bad spot.")
960         return
961     # do nothing if we are blocked
962     if game.quad[tid.i][tid.j] not in ('.', '#'):
963         return
964     here = copy.copy(game.tholian.location)
965     delta = (tid - game.tholian.location).sgn()
966     # move in x axis
967     while here.i != tid.i:
968         here.i += delta.i
969         if game.quad[here.i][here.j] == '.':
970             game.tholian.move(here)
971     # move in y axis
972     while here.j != tid.j:
973         here.j += delta.j
974         if game.quad[here.i][here.j] == '.':
975             game.tholian.move(here)
976     # check to see if all holes plugged
977     for i in range(QUADSIZE):
978         if game.quad[0][i] != '#' and game.quad[0][i] != 'T':
979             return
980         if game.quad[QUADSIZE-1][i] != '#' and game.quad[QUADSIZE-1][i] != 'T':
981             return
982         if game.quad[i][0] != '#' and game.quad[i][0] != 'T':
983             return
984         if game.quad[i][QUADSIZE-1] != '#' and game.quad[i][QUADSIZE-1] != 'T':
985             return
986     # All plugged up -- Tholian splits
987     game.quad[game.tholian.location.i][game.tholian.location.j] = '#'
988     dropin(' ')
989     prout(crmena(True, 'T', "sector", game.tholian) + _(" completes web."))
990     game.tholian.move(None)
991     return
992
993 # Code from battle.c begins here
994
995 def cloak():
996     "Change cloaking-device status."
997     if game.ship == 'F':
998         prout(_("Ye Faerie Queene hath no cloaking device."))
999         return
1000
1001     key = scanner.nexttok()
1002
1003     if key == "IHREAL":
1004         huh()
1005         return
1006
1007     action = None
1008     if key == "IHALPHA":
1009         if scanner.sees("on"):
1010             if game.iscloaked:
1011                 prout(_("The cloaking device has already been switched on."))
1012                 return
1013             action = "CLON"
1014         elif scanner.sees("off"):
1015             if not game.iscloaked:
1016                 prout(_("The cloaking device has already been switched off."))
1017                 return
1018             action = "CLOFF"
1019         else:
1020             huh()
1021             return
1022     else:
1023         if not game.iscloaked:
1024             proutn(_("Switch cloaking device on? "))
1025             if not ja():
1026                 return
1027             action = "CLON"
1028         else:
1029             proutn(_("Switch cloaking device off? "))
1030             if not ja():
1031                 return
1032             action = "CLOFF"
1033     if action is None:
1034         return
1035
1036     if action == "CLOFF":
1037         if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
1038             prout(_("Spock- \"Captain, the Treaty of Algeron is in effect.\n   Are you sure this is wise?\""))
1039             if not ja():
1040                 return
1041         prout("Engineer Scott- \"Aye, Sir.\"")
1042         game.iscloaked = False
1043         if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
1044             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
1045             game.ncviol += 1
1046             game.isviolreported = True
1047
1048             #if (neutz and game.state.date >= ALGERON) finish(FCLOAK);
1049             return
1050
1051     if action == "CLON":
1052         if damaged(DCLOAK):
1053             prout(_("Engineer Scott- \"The cloaking device is damaged, Sir.\""))
1054             return
1055
1056         if game.condition == "docked":
1057             prout(_("You cannot cloak while docked."))
1058
1059         if game.state.date >= ALGERON and not game.isviolreported:
1060             prout(_("Spock- \"Captain, using the cloaking device is a violation"))
1061             prout(_("  of the Treaty of Algeron. Considering the alternatives,"))
1062             proutn(_("  are you sure this is wise? "))
1063             if not ja():
1064                 return
1065         prout(_("Engineer Scott- \"Cloaking device has engaging, Sir...\""))
1066         attack(True)
1067         prout(_("Engineer Scott- \"Cloaking device has engaged, Sir.\""))
1068         game.iscloaked = True
1069
1070         if game.irhere and game.state.date >= ALGERON and not game.isviolreported:
1071             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
1072             game.ncviol += 1
1073             game.isviolreported = True
1074
1075 def doshield(shraise):
1076     "Change shield status."
1077     action = "NONE"
1078     game.ididit = False
1079     if shraise:
1080         action = "SHUP"
1081     else:
1082         key = scanner.nexttok()
1083         if key == "IHALPHA":
1084             if scanner.sees("transfer"):
1085                 action = "NRG"
1086             else:
1087                 if damaged(DSHIELD):
1088                     prout(_("Shields damaged and down."))
1089                     return
1090                 if scanner.sees("up"):
1091                     action = "SHUP"
1092                 elif scanner.sees("down"):
1093                     action = "SHDN"
1094         if action == "NONE":
1095             proutn(_("Do you wish to change shield energy? "))
1096             if ja():
1097                 action = "NRG"
1098             elif damaged(DSHIELD):
1099                 prout(_("Shields damaged and down."))
1100                 return
1101             elif game.shldup:
1102                 proutn(_("Shields are up. Do you want them down? "))
1103                 if ja():
1104                     action = "SHDN"
1105                 else:
1106                     scanner.chew()
1107                     return
1108             else:
1109                 proutn(_("Shields are down. Do you want them up? "))
1110                 if ja():
1111                     action = "SHUP"
1112                 else:
1113                     scanner.chew()
1114                     return
1115     if action == "SHUP": # raise shields
1116         if game.shldup:
1117             prout(_("Shields already up."))
1118             return
1119         game.shldup = True
1120         game.shldchg = True
1121         if game.condition != "docked":
1122             game.energy -= 50.0
1123         prout(_("Shields raised."))
1124         if game.energy <= 0:
1125             skip(1)
1126             prout(_("Shields raising uses up last of energy."))
1127             finish(FNRG)
1128             return
1129         game.ididit = True
1130         return
1131     elif action == "SHDN":
1132         if not game.shldup:
1133             prout(_("Shields already down."))
1134             return
1135         game.shldup = False
1136         game.shldchg = True
1137         prout(_("Shields lowered."))
1138         game.ididit = True
1139         return
1140     elif action == "NRG":
1141         while scanner.nexttok() != "IHREAL":
1142             scanner.chew()
1143             proutn(_("Energy to transfer to shields- "))
1144         nrg = scanner.real
1145         scanner.chew()
1146         if nrg == 0:
1147             return
1148         if nrg > game.energy:
1149             prout(_("Insufficient ship energy."))
1150             return
1151         game.ididit = True
1152         if game.shield+nrg >= game.inshld:
1153             prout(_("Shield energy maximized."))
1154             if game.shield+nrg > game.inshld:
1155                 prout(_("Excess energy requested returned to ship energy"))
1156             game.energy -= game.inshld-game.shield
1157             game.shield = game.inshld
1158             return
1159         if nrg < 0.0 and game.energy-nrg > game.inenrg:
1160             # Prevent shield drain loophole
1161             skip(1)
1162             prout(_("Engineering to bridge--"))
1163             prout(_("  Scott here. Power circuit problem, Captain."))
1164             prout(_("  I can't drain the shields."))
1165             game.ididit = False
1166             return
1167         if game.shield+nrg < 0:
1168             prout(_("All shield energy transferred to ship."))
1169             game.energy += game.shield
1170             game.shield = 0.0
1171             return
1172         proutn(_("Scotty- \""))
1173         if nrg > 0:
1174             prout(_("Transferring energy to shields.\""))
1175         else:
1176             prout(_("Draining energy from shields.\""))
1177         game.shield += nrg
1178         game.energy -= nrg
1179         return
1180
1181 def randdevice():
1182     "Choose a device to damage, at random."
1183     weights = (
1184         105,       # DSRSENS: short range scanners         10.5%
1185         105,       # DLRSENS: long range scanners          10.5%
1186         120,       # DPHASER: phasers                      12.0%
1187         120,       # DPHOTON: photon torpedoes             12.0%
1188         25,        # DLIFSUP: life support                  2.5%
1189         65,        # DWARPEN: warp drive                    6.5%
1190         70,        # DIMPULS: impulse engines               6.5%
1191         135,       # DSHIELD: deflector shields            13.5%
1192         30,        # DRADIO:  subspace radio                3.0%
1193         45,        # DSHUTTL: shuttle                       4.5%
1194         15,        # DCOMPTR: computer                      1.5%
1195         20,        # NAVCOMP: navigation system             2.0%
1196         75,        # DTRANSP: transporter                   7.5%
1197         20,        # DSHCTRL: high-speed shield controller  2.0%
1198         10,        # DDRAY: death ray                       1.0%
1199         30,        # DDSP: deep-space probes                3.0%
1200         10,        # DCLOAK: the cloaking device            1.0
1201     )
1202     assert(sum(weights) == 1000)
1203     idx = rnd.integer(1000)
1204     wsum = 0
1205     for (i, w) in enumerate(weights):
1206         wsum += w
1207         if idx < wsum:
1208             return i
1209     return None        # we should never get here
1210
1211 def collision(rammed, enemy):
1212     "Collision handling for rammong events."
1213     prouts(_("***RED ALERT!  RED ALERT!"))
1214     skip(1)
1215     prout(_("***COLLISION IMMINENT."))
1216     skip(2)
1217     proutn("***")
1218     proutn(crmshp())
1219     hardness = {'R':1.5, 'C':2.0, 'S':2.5, 'T':0.5, '?':4.0}.get(enemy.type, 1.0)
1220     if rammed:
1221         proutn(_(" rammed by "))
1222     else:
1223         proutn(_(" rams "))
1224     proutn(crmena(False, enemy.type, "sector", enemy.location))
1225     if rammed:
1226         proutn(_(" (original position)"))
1227     skip(1)
1228     deadkl(enemy.location, enemy.type, game.sector)
1229     proutn("***" + crmshp() + " heavily damaged.")
1230     icas = rnd.integer(10, 30)
1231     prout(_("***Sickbay reports %d casualties") % icas)
1232     game.casual += icas
1233     game.state.crew -= icas
1234     # In the pre-SST2K version, all devices got equiprobably damaged,
1235     # which was silly.  Instead, pick up to half the devices at
1236     # random according to our weighting table,
1237     ncrits = rnd.integer(NDEVICES//2)
1238     while ncrits > 0:
1239         ncrits -= 1
1240         dev = randdevice()
1241         if game.damage[dev] < 0:
1242             continue
1243         extradm = (10.0*hardness*rnd.real()+1.0)*game.damfac
1244         # Damage for at least time of travel!
1245         game.damage[dev] += game.optime + extradm
1246     game.shldup = False
1247     prout(_("***Shields are down."))
1248     if game.unwon():
1249         announce()
1250         damagereport()
1251     else:
1252         finish(FWON)
1253
1254 def torpedo(origin, bearing, dispersion, number, nburst):
1255     "Let a photon torpedo fly"
1256     if not damaged(DSRSENS) or game.condition == "docked":
1257         setwnd(srscan_window)
1258     else:
1259         setwnd(message_window)
1260     ac = bearing + 0.25*dispersion        # dispersion is a random variable
1261     bullseye = (15.0 - bearing)*0.5235988
1262     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin))
1263     bumpto = Coord(0, 0)
1264     # Loop to move a single torpedo
1265     setwnd(message_window)
1266     for step in range(1, QUADSIZE*2):
1267         if not track.nexttok():
1268             break
1269         w = track.sector()
1270         if not w.valid_sector():
1271             break
1272         iquad = game.quad[w.i][w.j]
1273         tracktorpedo(w, step, number, nburst, iquad)
1274         if iquad == '.':
1275             continue
1276         # hit something
1277         setwnd(message_window)
1278         if not damaged(DSRSENS) or game.condition == "docked":
1279             skip(1)        # start new line after text track
1280         if iquad in ('E', 'F'): # Hit our ship
1281             skip(1)
1282             prout(_("Torpedo hits %s.") % crmshp())
1283             hit = 700.0 + rnd.real(100) - \
1284                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1285             newcnd() # we're blown out of dock
1286             if game.landed or game.condition == "docked":
1287                 return hit # Cheat if on a planet
1288             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1289             # is 143 degrees, which is almost exactly 4.8 clockface units
1290             displacement = course(track.bearing+rnd.real(-2.4, 2.4), distance=2**0.5)
1291             displacement.nexttok()
1292             bumpto = displacement.sector()
1293             if not bumpto.valid_sector():
1294                 return hit
1295             if game.quad[bumpto.i][bumpto.j] == ' ':
1296                 finish(FHOLE)
1297                 return hit
1298             if game.quad[bumpto.i][bumpto.j] != '.':
1299                 # can't move into object
1300                 return hit
1301             game.sector = bumpto
1302             proutn(crmshp())
1303             game.quad[w.i][w.j] = '.'
1304             game.quad[bumpto.i][bumpto.j] = iquad
1305             prout(_(" displaced by blast to Sector %s ") % bumpto)
1306             for enemy in game.enemies:
1307                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1308             sortenemies()
1309             return None
1310         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy
1311             # find the enemy
1312             if iquad in ('C', 'S') and rnd.withprob(0.05):
1313                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1314                 prout(_("   torpedo neutralized."))
1315                 return None
1316             for enemy in game.enemies:
1317                 if w == enemy.location:
1318                     kp = math.fabs(enemy.power)
1319                     h1 = 700.0 + rnd.integer(100) - \
1320                         1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1321                     h1 = math.fabs(h1)
1322                     if kp < h1:
1323                         h1 = kp
1324                     if enemy.power < 0:
1325                         enemy.power -= -h1
1326                     else:
1327                         enemy.power -= h1
1328                     if enemy.power == 0:
1329                         deadkl(w, iquad, w)
1330                         return None
1331                     proutn(crmena(True, iquad, "sector", w))
1332                     displacement = course(track.bearing+rnd.real(-2.4, 2.4), distance=2**0.5)
1333                     displacement.nexttok()
1334                     bumpto = displacement.sector()
1335                     if not bumpto.valid_sector():
1336                         prout(_(" damaged but not destroyed."))
1337                         return None
1338                     if game.quad[bumpto.i][bumpto.j] == ' ':
1339                         prout(_(" buffeted into black hole."))
1340                         deadkl(w, iquad, bumpto)
1341                     if game.quad[bumpto.i][bumpto.j] != '.':
1342                         prout(_(" damaged but not destroyed."))
1343                     else:
1344                         prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1345                         enemy.location = bumpto
1346                         game.quad[w.i][w.j] = '.'
1347                         game.quad[bumpto.i][bumpto.j] = iquad
1348                         for tenemy in game.enemies:
1349                             tenemy.kdist = tenemy.kavgd = (game.sector-tenemy.location).distance()
1350                         sortenemies()
1351                     break
1352             else:
1353                 prout("Internal error, no enemy where expected!")
1354                 raise SystemExit(1)
1355             return None
1356         elif iquad == 'B': # Hit a base
1357             skip(1)
1358             prout(_("***STARBASE DESTROYED.."))
1359             game.state.baseq = [x for x in game.state.baseq if x != game.quadrant]
1360             game.quad[w.i][w.j] = '.'
1361             game.base.invalidate()
1362             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
1363             game.state.chart[game.quadrant.i][game.quadrant.j].starbase = False
1364             game.state.basekl += 1
1365             newcnd()
1366             return None
1367         elif iquad == 'P': # Hit a planet
1368             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1369             game.state.nplankl += 1
1370             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1371             game.iplnet.pclass = "destroyed"
1372             game.iplnet = None
1373             game.plnet.invalidate()
1374             game.quad[w.i][w.j] = '.'
1375             if game.landed:
1376                 # captain perishes on planet
1377                 finish(FDPLANET)
1378             return None
1379         elif iquad == '@': # Hit an inhabited world -- very bad!
1380             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1381             game.state.nworldkl += 1
1382             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1383             game.iplnet.pclass = "destroyed"
1384             game.iplnet = None
1385             game.plnet.invalidate()
1386             game.quad[w.i][w.j] = '.'
1387             if game.landed:
1388                 # captain perishes on planet
1389                 finish(FDPLANET)
1390             prout(_("The torpedo destroyed an inhabited planet."))
1391             return None
1392         elif iquad == '*': # Hit a star
1393             if rnd.withprob(0.9):
1394                 nova(w)
1395             else:
1396                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1397             return None
1398         elif iquad == '?': # Hit a thingy
1399             if not (game.options & OPTION_THINGY) or rnd.withprob(0.3):
1400                 skip(1)
1401                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1402                 skip(1)
1403                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1404                 skip(1)
1405                 proutn(_("Mr. Spock-"))
1406                 prouts(_("  \"Fascinating!\""))
1407                 skip(1)
1408                 deadkl(w, iquad, w)
1409             else:
1410                 # Stas Sergeev added the possibility that
1411                 # you can shove the Thingy and piss it off.
1412                 # It then becomes an enemy and may fire at you.
1413                 thing.angry()
1414             return None
1415         elif iquad == ' ': # Black hole
1416             skip(1)
1417             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1418             return None
1419         elif iquad == '#': # hit the web
1420             skip(1)
1421             prout(_("***Torpedo absorbed by Tholian web."))
1422             return None
1423         elif iquad == 'T':  # Hit a Tholian
1424             h1 = 700.0 + rnd.integer(100) - \
1425                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1426             h1 = math.fabs(h1)
1427             if h1 >= 600:
1428                 game.quad[w.i][w.j] = '.'
1429                 deadkl(w, iquad, w)
1430                 game.tholian = None
1431                 return None
1432             skip(1)
1433             proutn(crmena(True, 'T', "sector", w))
1434             if rnd.withprob(0.05):
1435                 prout(_(" survives photon blast."))
1436                 return None
1437             prout(_(" disappears."))
1438             game.tholian.move(None)
1439             game.quad[w.i][w.j] = '#'
1440             dropin(' ')
1441             return None
1442         else: # Problem!
1443             skip(1)
1444             proutn("Don't know how to handle torpedo collision with ")
1445             proutn(crmena(True, iquad, "sector", w))
1446             skip(1)
1447             return None
1448         break
1449     skip(1)
1450     setwnd(message_window)
1451     prout(_("Torpedo missed."))
1452     return None
1453
1454 def fry(hit):
1455     "Critical-hit resolution."
1456     if hit < (275.0-25.0*game.skill)*rnd.real(1.0, 1.5):
1457         return
1458     ncrit = int(1.0 + hit/(500.0+rnd.real(100)))
1459     proutn(_("***CRITICAL HIT--"))
1460     # Select devices and cause damage
1461     cdam = []
1462     while ncrit > 0:
1463         while True:
1464             j = randdevice()
1465             # Cheat to prevent shuttle damage unless on ship
1466             if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship") or (j == DCLOAK and game.ship != 'E' or j == DDRAY)):
1467                 break
1468         cdam.append(j)
1469         extradm = (hit*game.damfac)/(ncrit*rnd.real(75, 100))
1470         game.damage[j] += extradm
1471         ncrit -= 1
1472     skipcount = 0
1473     for (i, j) in enumerate(cdam):
1474         proutn(device[j])
1475         if skipcount % 3 == 2 and i < len(cdam)-1:
1476             skip(1)
1477         skipcount += 1
1478         if i < len(cdam)-1:
1479             proutn(_(" and "))
1480     prout(_(" damaged."))
1481     if damaged(DSHIELD) and game.shldup:
1482         prout(_("***Shields knocked down."))
1483         game.shldup = False
1484     if damaged(DCLOAK) and game.iscloaked:
1485         prout(_("***Cloaking device rendered inoperative."))
1486         game.iscloaked = False
1487
1488 def attack(torps_ok):
1489     # bad guy attacks us
1490     # torps_ok == False forces use of phasers in an attack
1491     if game.iscloaked:
1492         return
1493     # game could be over at this point, check
1494     if game.alldone:
1495         return
1496     attempt = False
1497     ihurt = False
1498     hitmax = 0.0
1499     hittot = 0.0
1500     chgfac = 1.0
1501     where = "neither"
1502     if game.idebug:
1503         prout("=== ATTACK!")
1504     # Tholian gets to move before attacking
1505     if game.tholian:
1506         movetholian()
1507     # if you have just entered the RNZ, you'll get a warning
1508     if game.neutz: # The one chance not to be attacked
1509         game.neutz = False
1510         return
1511     # commanders get a chance to tac-move towards you
1512     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:
1513         for (bugout, enemy, old, goto) in  moveklings():
1514             if bugout:
1515                 # we know about this if either short or long range
1516                 # sensors are working
1517                 if damaged(DSRSENS) and damaged(DLRSENS) \
1518                        and game.condition != "docked":
1519                     prout(crmena(True, enemy.type, "sector", old) + \
1520                           (_(" escapes to Quadrant %s (and regains strength).") % goto))
1521             else: # Enemy still in-sector
1522                 if enemy.move(goto):
1523                     if not damaged(DSRSENS) or game.condition == "docked":
1524                         proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
1525                         if enemy.kdist < old:
1526                             proutn(_(" advances to "))
1527                         else:
1528                             proutn(_(" retreats to "))
1529                         prout("Sector %s." % goto)
1530         sortenemies()
1531     # if no enemies remain after movement, we're done
1532     if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
1533         return
1534     # set up partial hits if attack happens during shield status change
1535     pfac = 1.0/game.inshld
1536     if game.shldchg:
1537         chgfac = 0.25 + rnd.real(0.5)
1538     skip(1)
1539     # message verbosity control
1540     if game.skill <= SKILL_FAIR:
1541         where = "sector"
1542     for enemy in game.enemies:
1543         if enemy.power < 0:
1544             continue        # too weak to attack
1545         # compute hit strength and diminish shield power
1546         r = rnd.real()
1547         # Increase chance of photon torpedos if docked or enemy energy is low
1548         if game.condition == "docked":
1549             r *= 0.25
1550         if enemy.power < 500:
1551             r *= 0.25
1552         if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
1553             continue
1554         # different enemies have different probabilities of throwing a torp
1555         usephasers = not torps_ok or \
1556             (enemy.type == 'K' and r > 0.0005) or \
1557             (enemy.type == 'C' and r > 0.015) or \
1558             (enemy.type == 'R' and r > 0.3) or \
1559             (enemy.type == 'S' and r > 0.07) or \
1560             (enemy.type == '?' and r > 0.05)
1561         if usephasers:            # Enemy uses phasers
1562             if game.condition == "docked":
1563                 continue # Don't waste the effort!
1564             attempt = True # Attempt to attack
1565             dustfac = rnd.real(0.8, 0.85)
1566             hit = enemy.power*math.pow(dustfac, enemy.kavgd)
1567             enemy.power *= 0.75
1568         else: # Enemy uses photon torpedo
1569             # We should be able to make the bearing() method work here
1570             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1571             hit = 0
1572             proutn(_("***TORPEDO INCOMING"))
1573             if not damaged(DSRSENS):
1574                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1575             attempt = True
1576             prout("  ")
1577             dispersion = (rnd.real()+rnd.real())*0.5 - 0.5
1578             dispersion += 0.002*enemy.power*dispersion
1579             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1580             if game.unwon() == 0:
1581                 finish(FWON) # Klingons did themselves in!
1582             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1583                 return # Supernova or finished
1584             if hit is None:
1585                 continue
1586         # incoming phaser or torpedo, shields may dissipate it
1587         if game.shldup or game.shldchg or game.condition == "docked":
1588             # shields will take hits
1589             propor = pfac * game.shield
1590             if game.condition == "docked":
1591                 propor *= 2.1
1592             if propor < 0.1:
1593                 propor = 0.1
1594             hitsh = propor*chgfac*hit+1.0
1595             absorb = 0.8*hitsh
1596             if absorb > game.shield:
1597                 absorb = game.shield
1598             game.shield -= absorb
1599             hit -= hitsh
1600             # taking a hit blasts us out of a starbase dock
1601             if game.condition == "docked":
1602                 dock(False)
1603             # but the shields may take care of it
1604             if propor > 0.1 and hit < 0.005*game.energy:
1605                 continue
1606         # hit from this opponent got through shields, so take damage
1607         ihurt = True
1608         proutn(_("%d unit hit") % int(hit))
1609         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1610             proutn(_(" on the ") + crmshp())
1611         if not damaged(DSRSENS) and usephasers:
1612             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1613         skip(1)
1614         # Decide if hit is critical
1615         if hit > hitmax:
1616             hitmax = hit
1617         hittot += hit
1618         fry(hit)
1619         game.energy -= hit
1620     if game.energy <= 0:
1621         # Returning home upon your shield, not with it...
1622         finish(FBATTLE)
1623         return
1624     if not attempt and game.condition == "docked":
1625         prout(_("***Enemies decide against attacking your ship."))
1626     percent = 100.0*pfac*game.shield+0.5
1627     if not ihurt:
1628         # Shields fully protect ship
1629         proutn(_("Enemy attack reduces shield strength to "))
1630     else:
1631         # Emit message if starship suffered hit(s)
1632         skip(1)
1633         proutn(_("Energy left %2d    shields ") % int(game.energy))
1634         if game.shldup:
1635             proutn(_("up "))
1636         elif not damaged(DSHIELD):
1637             proutn(_("down "))
1638         else:
1639             proutn(_("damaged, "))
1640     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1641     # Check if anyone was hurt
1642     if hitmax >= 200 or hittot >= 500:
1643         icas = rnd.integer(int(hittot * 0.015))
1644         if icas >= 2:
1645             skip(1)
1646             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1647             prout(_("   in that last attack.\""))
1648             game.casual += icas
1649             game.state.crew -= icas
1650     # After attack, reset average distance to enemies
1651     for enemy in game.enemies:
1652         enemy.kavgd = enemy.kdist
1653     sortenemies()
1654     return
1655
1656 def deadkl(w, etype, mv):
1657     "Kill a Klingon, Tholian, Romulan, or Thingy."
1658     # Added mv to allow enemy to "move" before dying
1659     proutn(crmena(True, etype, "sector", mv))
1660     # Decide what kind of enemy it is and update appropriately
1661     if etype == 'R':
1662         # Chalk up a Romulan
1663         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1664         game.irhere -= 1
1665         game.state.nromrem -= 1
1666     elif etype == 'T':
1667         # Killed a Tholian
1668         game.tholian = None
1669     elif etype == '?':
1670         # Killed a Thingy
1671         global thing
1672         thing = None
1673     else:
1674         # Killed some type of Klingon
1675         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1676         game.klhere -= 1
1677         if etype == 'C':
1678             game.state.kcmdr.remove(game.quadrant)
1679             unschedule(FTBEAM)
1680             if game.state.kcmdr:
1681                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1682             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1683                 unschedule(FCDBAS)
1684         elif etype ==  'K':
1685             pass
1686         elif etype ==  'S':
1687             game.state.nscrem -= 1
1688             game.state.kscmdr.invalidate()
1689             game.isatb = 0
1690             game.iscate = False
1691             unschedule(FSCMOVE)
1692             unschedule(FSCDBAS)
1693     # For each kind of enemy, finish message to player
1694     prout(_(" destroyed."))
1695     if game.unwon() == 0:
1696         return
1697     game.recompute()
1698     # Remove enemy ship from arrays describing local conditions
1699     for e in game.enemies:
1700         if e.location == w:
1701             e.move(None)
1702             break
1703     return
1704
1705 def targetcheck(w):
1706     "Return None if target is invalid, otherwise return a course angle."
1707     if not w.valid_sector():
1708         huh()
1709         return None
1710     delta = Coord()
1711     # C code this was translated from is wacky -- why the sign reversal?
1712     delta.j = (w.j - game.sector.j)
1713     delta.i = (game.sector.i - w.i)
1714     if delta == Coord(0, 0):
1715         skip(1)
1716         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1717         prout(_("  I recommend an immediate review of"))
1718         prout(_("  the Captain's psychological profile.\""))
1719         scanner.chew()
1720         return None
1721     return delta.bearing()
1722
1723 def torps():
1724     "Launch photon torpedo salvo."
1725     tcourse = []
1726     game.ididit = False
1727     if damaged(DPHOTON):
1728         prout(_("Photon tubes damaged."))
1729         scanner.chew()
1730         return
1731     if game.torps == 0:
1732         prout(_("No torpedoes left."))
1733         scanner.chew()
1734         return
1735     # First, get torpedo count
1736     while True:
1737         scanner.nexttok()
1738         if scanner.token == "IHALPHA":
1739             huh()
1740             return
1741         elif scanner.token == "IHEOL" or not scanner.waiting():
1742             prout(_("%d torpedoes left.") % game.torps)
1743             scanner.chew()
1744             proutn(_("Number of torpedoes to fire- "))
1745             continue        # Go back around to get a number
1746         else: # key == "IHREAL"
1747             try:
1748                 n = scanner.int()
1749             except TypeError:
1750                 huh()
1751                 return
1752             if n <= 0: # abort command
1753                 scanner.chew()
1754                 return
1755             if n > MAXBURST:
1756                 scanner.chew()
1757                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1758                 return
1759             if n > game.torps:
1760                 scanner.chew()        # User requested more torps than available
1761                 continue        # Go back around
1762             break        # All is good, go to next stage
1763     # Next, get targets
1764     target = []
1765     for i in range(n):
1766         key = scanner.nexttok()
1767         if i == 0 and key == "IHEOL":
1768             break        # no coordinate waiting, we will try prompting
1769         if i == 1 and key == "IHEOL":
1770             # direct all torpedoes at one target
1771             while i < n:
1772                 target.append(target[0])
1773                 tcourse.append(tcourse[0])
1774                 i += 1
1775             break
1776         scanner.push(scanner.token)
1777         target.append(scanner.getcoord())
1778         if target[-1] is None:
1779             return
1780         tcourse.append(targetcheck(target[-1]))
1781         if tcourse[-1] is None:
1782             return
1783     scanner.chew()
1784     if len(target) == 0:
1785         # prompt for each one
1786         for i in range(n):
1787             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1788             scanner.chew()
1789             target.append(scanner.getcoord())
1790             if target[-1] is None:
1791                 return
1792             tcourse.append(targetcheck(target[-1]))
1793             if tcourse[-1] is None:
1794                 return
1795     game.ididit = True
1796     # Loop for moving <n> torpedoes
1797     for i in range(n):
1798         if game.condition != "docked":
1799             game.torps -= 1
1800         dispersion = (rnd.real()+rnd.real())*0.5 -0.5
1801         if math.fabs(dispersion) >= 0.47:
1802             # misfire!
1803             dispersion *= rnd.real(1.2, 2.2)
1804             if n > 0:
1805                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1806             else:
1807                 prouts(_("***TORPEDO MISFIRES."))
1808             skip(1)
1809             if i < n:
1810                 prout(_("  Remainder of burst aborted."))
1811             if rnd.withprob(0.2):
1812                 prout(_("***Photon tubes damaged by misfire."))
1813                 game.damage[DPHOTON] = game.damfac * rnd.real(1.0, 3.0)
1814             break
1815         if game.iscloaked:
1816             dispersion *= 1.2
1817         elif game.shldup or game.condition == "docked":
1818             dispersion *= 1.0 + 0.0001*game.shield
1819         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1820         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1821             return
1822     if game.unwon()<=0:
1823         finish(FWON)
1824
1825 def overheat(rpow):
1826     "Check for phasers overheating."
1827     if rpow > 1500:
1828         checkburn = (rpow-1500.0)*0.00038
1829         if rnd.withprob(checkburn):
1830             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1831             game.damage[DPHASER] = game.damfac* rnd.real(1.0, 2.0) * (1.0+checkburn)
1832
1833 def checkshctrl(rpow):
1834     "Check shield control."
1835     skip(1)
1836     if rnd.withprob(0.998):
1837         prout(_("Shields lowered."))
1838         return False
1839     # Something bad has happened
1840     prouts(_("***RED ALERT!  RED ALERT!"))
1841     skip(2)
1842     hit = rpow*game.shield/game.inshld
1843     game.energy -= rpow+hit*0.8
1844     game.shield -= hit*0.2
1845     if game.energy <= 0.0:
1846         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1847         skip(1)
1848         stars()
1849         finish(FPHASER)
1850         return True
1851     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1852     skip(2)
1853     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1854     icas = rnd.integer(int(hit*0.012))
1855     skip(1)
1856     fry(0.8*hit)
1857     if icas:
1858         skip(1)
1859         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1860         prout(_("  %d casualties so far.\"") % icas)
1861         game.casual += icas
1862         game.state.crew -= icas
1863     skip(1)
1864     prout(_("Phaser energy dispersed by shields."))
1865     prout(_("Enemy unaffected."))
1866     overheat(rpow)
1867     return True
1868
1869 def hittem(hits):
1870     "Register a phaser hit on Klingons and Romulans."
1871     w = Coord()
1872     skip(1)
1873     kk = 0
1874     for wham in hits:
1875         if wham == 0:
1876             continue
1877         dustfac = rnd.real(0.9, 1.0)
1878         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1879         kpini = game.enemies[kk].power
1880         kp = math.fabs(kpini)
1881         if PHASEFAC*hit < kp:
1882             kp = PHASEFAC*hit
1883         if game.enemies[kk].power < 0:
1884             game.enemies[kk].power -= -kp
1885         else:
1886             game.enemies[kk].power -= kp
1887         kpow = game.enemies[kk].power
1888         w = game.enemies[kk].location
1889         if hit > 0.005:
1890             if not damaged(DSRSENS):
1891                 boom(w)
1892             proutn(_("%d unit hit on ") % int(hit))
1893         else:
1894             proutn(_("Very small hit on "))
1895         ienm = game.quad[w.i][w.j]
1896         if ienm == '?':
1897             thing.angry()
1898         proutn(crmena(False, ienm, "sector", w))
1899         skip(1)
1900         if kpow == 0:
1901             deadkl(w, ienm, w)
1902             if game.unwon()==0:
1903                 finish(FWON)
1904             if game.alldone:
1905                 return
1906             continue
1907         else: # decide whether or not to emasculate klingon
1908             # pylint: disable=chained-comparison
1909             if kpow > 0 and rnd.withprob(0.9) and kpow <= rnd.real(0.4, 0.8)*kpini:
1910                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1911                 prout(_("   has just lost its firepower.\""))
1912                 game.enemies[kk].power = -kpow
1913         kk += 1
1914     return
1915
1916 def phasers():
1917     "Fire phasers at bad guys."
1918     hits = []
1919     kz = 0
1920     k = 1
1921     irec = 0 # Cheating inhibitor
1922     ifast = False
1923     no = False
1924     itarg = True
1925     msgflag = True
1926     rpow = 0.0
1927     automode = "NOTSET"
1928     key = ""
1929     skip(1)
1930     # SR sensors and Computer are needed for automode
1931     if damaged(DSRSENS) or damaged(DCOMPTR):
1932         itarg = False
1933     if game.condition == "docked":
1934         prout(_("Phasers can't be fired through base shields."))
1935         scanner.chew()
1936         return
1937     if damaged(DPHASER):
1938         prout(_("Phaser control damaged."))
1939         scanner.chew()
1940         return
1941     if game.shldup:
1942         if damaged(DSHCTRL):
1943             prout(_("High speed shield control damaged."))
1944             scanner.chew()
1945             return
1946         if game.energy <= 200.0:
1947             prout(_("Insufficient energy to activate high-speed shield control."))
1948             scanner.chew()
1949             return
1950         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1951         ifast = True
1952     # Original code so convoluted, I re-did it all
1953     # (That was Tom Almy talking about the C code, I think -- ESR)
1954     while automode == "NOTSET":
1955         key = scanner.nexttok()
1956         if key == "IHALPHA":
1957             if scanner.sees("manual"):
1958                 if len(game.enemies)==0:
1959                     prout(_("There is no enemy present to select."))
1960                     scanner.chew()
1961                     key = "IHEOL"
1962                     automode = "AUTOMATIC"
1963                 else:
1964                     automode = "MANUAL"
1965                     key = scanner.nexttok()
1966             elif scanner.sees("automatic"):
1967                 if (not itarg) and len(game.enemies) != 0:
1968                     automode = "FORCEMAN"
1969                 else:
1970                     if len(game.enemies)==0:
1971                         prout(_("Energy will be expended into space."))
1972                     automode = "AUTOMATIC"
1973                     key = scanner.nexttok()
1974             elif scanner.sees("no"):
1975                 no = True
1976             else:
1977                 huh()
1978                 return
1979         elif key == "IHREAL":
1980             if len(game.enemies)==0:
1981                 prout(_("Energy will be expended into space."))
1982                 automode = "AUTOMATIC"
1983             elif not itarg:
1984                 automode = "FORCEMAN"
1985             else:
1986                 automode = "AUTOMATIC"
1987         else:
1988             # "IHEOL"
1989             if len(game.enemies)==0:
1990                 prout(_("Energy will be expended into space."))
1991                 automode = "AUTOMATIC"
1992             elif not itarg:
1993                 automode = "FORCEMAN"
1994             else:
1995                 proutn(_("Manual or automatic? "))
1996                 scanner.chew()
1997     avail = game.energy
1998     if ifast:
1999         avail -= 200.0
2000     if automode == "AUTOMATIC":
2001         if key == "IHALPHA" and scanner.sees("no"):
2002             no = True
2003             key = scanner.nexttok()
2004         if key != "IHREAL" and len(game.enemies) != 0:
2005             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
2006         irec = 0
2007         while True:
2008             scanner.chew()
2009             if not kz:
2010                 for i in range(len(game.enemies)):
2011                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*rnd.real(1.01, 1.06) + 1.0
2012             kz = 1
2013             proutn(_("%d units required. ") % irec)
2014             scanner.chew()
2015             proutn(_("Units to fire= "))
2016             key = scanner.nexttok()
2017             if key != "IHREAL":
2018                 return
2019             rpow = scanner.real
2020             if rpow > avail:
2021                 proutn(_("Energy available= %.2f") % avail)
2022                 skip(1)
2023                 key = "IHEOL"
2024             if not rpow > avail:
2025                 break
2026         if rpow <= 0:
2027             # chicken out
2028             scanner.chew()
2029             return
2030         key = scanner.nexttok()
2031         if key == "IHALPHA" and scanner.sees("no"):
2032             no = True
2033         if ifast:
2034             game.energy -= 200 # Go and do it!
2035             if checkshctrl(rpow):
2036                 return
2037         scanner.chew()
2038         game.energy -= rpow
2039         extra = rpow
2040         if len(game.enemies):
2041             extra = 0.0
2042             powrem = rpow
2043             for i in range(len(game.enemies)):
2044                 hits.append(0.0)
2045                 if powrem <= 0:
2046                     continue
2047                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
2048                 over = rnd.real(1.01, 1.06) * hits[i]
2049                 temp = powrem
2050                 powrem -= hits[i] + over
2051                 if powrem <= 0 and temp < hits[i]:
2052                     hits[i] = temp
2053                 if powrem <= 0:
2054                     over = 0.0
2055                 extra += over
2056             if powrem > 0.0:
2057                 extra += powrem
2058             hittem(hits)
2059             game.ididit = True
2060         if extra > 0 and not game.alldone:
2061             if game.tholian:
2062                 proutn(_("*** Tholian web absorbs "))
2063                 if len(game.enemies)>0:
2064                     proutn(_("excess "))
2065                 prout(_("phaser energy."))
2066             else:
2067                 prout(_("%d expended on empty space.") % int(extra))
2068     elif automode == "FORCEMAN":
2069         scanner.chew()
2070         key = "IHEOL"
2071         if damaged(DCOMPTR):
2072             prout(_("Battle computer damaged, manual fire only."))
2073         else:
2074             skip(1)
2075             prouts(_("---WORKING---"))
2076             skip(1)
2077             prout(_("Short-range-sensors-damaged"))
2078             prout(_("Insufficient-data-for-automatic-phaser-fire"))
2079             prout(_("Manual-fire-must-be-used"))
2080             skip(1)
2081     elif automode == "MANUAL":
2082         rpow = 0.0
2083         for k in range(len(game.enemies)):
2084             aim = game.enemies[k].location
2085             ienm = game.quad[aim.i][aim.j]
2086             if msgflag:
2087                 proutn(_("Energy available= %.2f") % (avail-0.006))
2088                 skip(1)
2089                 msgflag = False
2090                 rpow = 0.0
2091             if damaged(DSRSENS) and \
2092                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
2093                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2094                 scanner.chew()
2095                 key = "IHEOL"
2096                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
2097                 continue
2098             if key == "IHEOL":
2099                 scanner.chew()
2100                 if itarg and k > kz:
2101                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        rnd.real(1.01, 1.06) + 1.0
2102                 kz = k
2103                 proutn("(")
2104                 if not damaged(DCOMPTR):
2105                     proutn("%d" % irec)
2106                 else:
2107                     proutn("??")
2108                 proutn(")  ")
2109                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
2110                 key = scanner.nexttok()
2111             if key == "IHALPHA" and scanner.sees("no"):
2112                 no = True
2113                 key = scanner.nexttok()
2114                 continue
2115             if key == "IHALPHA":
2116                 huh()
2117                 return
2118             if key == "IHEOL":
2119                 if k == 1: # Let me say I'm baffled by this
2120                     msgflag = True
2121                 continue
2122             if scanner.real < 0:
2123                 # abort out
2124                 scanner.chew()
2125                 return
2126             hits.append(scanner.real)
2127             rpow += scanner.real
2128             # If total requested is too much, inform and start over
2129             if rpow > avail:
2130                 prout(_("Available energy exceeded -- try again."))
2131                 scanner.chew()
2132                 return
2133             key = scanner.nexttok() # scan for next value
2134         if rpow == 0.0:
2135             # zero energy -- abort
2136             scanner.chew()
2137             return
2138         if key == "IHALPHA" and scanner.sees("no"):
2139             no = True
2140         game.energy -= rpow
2141         scanner.chew()
2142         if ifast:
2143             game.energy -= 200.0
2144             if checkshctrl(rpow):
2145                 return
2146         hittem(hits)
2147         game.ididit = True
2148      # Say shield raised or malfunction, if necessary
2149     if game.alldone:
2150         return
2151     if ifast:
2152         skip(1)
2153         if no == 0:
2154             if rnd.withprob(0.01):
2155                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2156                 prouts(_("         CLICK   CLICK   POP  . . ."))
2157                 prout(_(" No response, sir!"))
2158                 game.shldup = False
2159             else:
2160                 prout(_("Shields raised."))
2161         else:
2162             game.shldup = False
2163     overheat(rpow)
2164
2165
2166 def capture():
2167     game.ididit = False # Nothing if we fail
2168     game.optime = 0.0
2169
2170     # Make sure there is room in the brig
2171     if game.brigfree == 0:
2172         prout(_("Security reports the brig is already full."))
2173         return
2174
2175     if damaged(DRADIO):
2176         prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
2177         return
2178
2179     if damaged(DTRANSP):
2180         prout(_("Scotty- \"Transporter damaged, sir.\""))
2181         return
2182
2183     # find out if there are any at all
2184     if game.klhere < 1:
2185         prout(_("Uhura- \"Getting no response, sir.\""))
2186         return
2187
2188     # if there is more than one Klingon, find out which one
2189     #   Cruddy, just takes one at random.  Should ask the captain.
2190     #   Nah, just select the weakest one since it is most likely to
2191     #   surrender (Tom Almy mod)
2192     klingons = [e for e in game.enemies if e.type == 'K']
2193     weakest = sorted(klingons, key=lambda e: e.power)[0]
2194     game.optime = 0.05          # This action will take some time
2195     game.ididit = True #  So any others can strike back
2196
2197     # check out that Klingon
2198     # The algorithm isn't that great and could use some more
2199     # intelligent design
2200     # x = 300 + 25*skill;
2201     x = game.energy / (weakest.power * len(klingons))
2202     #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
2203     #      % (game.energy, weakest.power, len(klingons)))
2204     x *= 2.5    # would originally have been equivalent of 1.4,
2205                 # but we want command to work more often, more humanely
2206     #prout(_("Prob = %.4f" % x))
2207     #   x = 100; // For testing, of course!
2208     if x < rnd.real(100):
2209         # guess what, he surrendered!!!
2210         prout(_("Klingon captain at %s surrenders.") % weakest.location)
2211         i = rnd.real(200)
2212         if i > 0:
2213             prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
2214         if i > game.brigfree:
2215             prout(_("%d Klingons die because there is no room for them in the brig.") % (i-game.brigfree))
2216             i = game.brigfree
2217         game.brigfree -= i
2218         prout(_("%d captives taken") % i)
2219         deadkl(weakest.location, weakest.type, game.sector)
2220         if game.unwon()<=0:
2221             finish(FWON)
2222         return
2223
2224         # big surprise, he refuses to surrender
2225     prout(_("Fat chance, captain!"))
2226
2227 # Code from events.c begins here.
2228
2229 # This isn't a real event queue a la BSD Trek yet -- you can only have one
2230 # event of each type active at any given time.  Mostly these means we can
2231 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2232 # BSD Trek, from which we swiped the idea, can have up to 5.
2233
2234 def unschedule(evtype):
2235     "Remove an event from the schedule."
2236     game.future[evtype].date = FOREVER
2237     return game.future[evtype]
2238
2239 def is_scheduled(evtype):
2240     "Is an event of specified type scheduled."
2241     return game.future[evtype].date != FOREVER
2242
2243 def scheduled(evtype):
2244     "When will this event happen?"
2245     return game.future[evtype].date
2246
2247 def schedule(evtype, offset):
2248     "Schedule an event of specified type."
2249     game.future[evtype].date = game.state.date + offset
2250     return game.future[evtype]
2251
2252 def postpone(evtype, offset):
2253     "Postpone a scheduled event."
2254     game.future[evtype].date += offset
2255
2256 def cancelrest():
2257     "Rest period is interrupted by event."
2258     if game.resting:
2259         skip(1)
2260         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2261         if ja():
2262             game.resting = False
2263             game.optime = 0.0
2264             return True
2265     return False
2266
2267 def events():
2268     "Run through the event queue looking for things to do."
2269     i = 0
2270     fintim = game.state.date + game.optime
2271     yank = 0
2272     ictbeam = False
2273     istract = False
2274     w = Coord()
2275     hold = Coord()
2276     ev = Event()
2277     ev2 = Event()
2278
2279     def tractorbeam(yank):
2280         "Tractor-beaming cases merge here."
2281         announce()
2282         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2283         skip(1)
2284         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2285         # If Kirk & Co. screwing around on planet, handle
2286         atover(True) # atover(true) is Grab
2287         if game.alldone:
2288             return
2289         if game.icraft: # Caught in Galileo?
2290             finish(FSTRACTOR)
2291             return
2292         # Check to see if shuttle is aboard
2293         if game.iscraft == "offship":
2294             skip(1)
2295             if rnd.withprob(0.5):
2296                 prout(_("Galileo, left on the planet surface, is captured"))
2297                 prout(_("by aliens and made into a flying McDonald's."))
2298                 game.damage[DSHUTTL] = -10
2299                 game.iscraft = "removed"
2300             else:
2301                 prout(_("Galileo, left on the planet surface, is well hidden."))
2302         if evcode == FSPY:
2303             game.quadrant = game.state.kscmdr
2304         else:
2305             game.quadrant = game.state.kcmdr[i]
2306         game.sector = randplace(QUADSIZE)
2307         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2308                % (game.quadrant, game.sector))
2309         if game.resting:
2310             prout(_("(Remainder of rest/repair period cancelled.)"))
2311             game.resting = False
2312         if not game.shldup:
2313             if not damaged(DSHIELD) and game.shield > 0:
2314                 doshield(shraise=True) # raise shields
2315                 game.shldchg = False
2316             else:
2317                 prout(_("(Shields not currently useable.)"))
2318         newqad()
2319         # Adjust finish time to time of tractor beaming?
2320         # fintim = game.state.date+game.optime
2321         attack(torps_ok=False)
2322         if not game.state.kcmdr:
2323             unschedule(FTBEAM)
2324         else:
2325             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2326
2327     def destroybase():
2328         "Code merges here for any commander destroying a starbase."
2329         # Not perfect, but will have to do
2330         # Handle case where base is in same quadrant as starship
2331         if game.battle == game.quadrant:
2332             game.state.chart[game.battle.i][game.battle.j].starbase = False
2333             game.quad[game.base.i][game.base.j] = '.'
2334             game.base.invalidate()
2335             newcnd()
2336             skip(1)
2337             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2338         elif game.state.baseq and communicating():
2339             # Get word via subspace radio
2340             announce()
2341             skip(1)
2342             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2343             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2344             if game.isatb == 2:
2345                 prout(_("the Klingon Super-Commander"))
2346             else:
2347                 prout(_("a Klingon Commander"))
2348             game.state.chart[game.battle.i][game.battle.j].starbase = False
2349         # Remove Starbase from galaxy
2350         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2351         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2352         if game.isatb == 2:
2353             # reinstate a commander's base attack
2354             game.battle = hold
2355             game.isatb = 0
2356         else:
2357             game.battle.invalidate()
2358     if game.idebug:
2359         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2360         for i in range(1, NEVENTS):
2361             if   i == FSNOVA:  proutn("=== Supernova       ")
2362             elif i == FTBEAM:  proutn("=== T Beam          ")
2363             elif i == FSNAP:   proutn("=== Snapshot        ")
2364             elif i == FBATTAK: proutn("=== Base Attack     ")
2365             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2366             elif i == FSCMOVE: proutn("=== SC Move         ")
2367             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2368             elif i == FDSPROB: proutn("=== Probe Move      ")
2369             elif i == FDISTR:  proutn("=== Distress Call   ")
2370             elif i == FENSLV:  proutn("=== Enslavement     ")
2371             elif i == FREPRO:  proutn("=== Klingon Build   ")
2372             if is_scheduled(i):
2373                 prout("%.2f" % (scheduled(i)))
2374             else:
2375                 prout("never")
2376     radio_was_broken = damaged(DRADIO)
2377     hold.i = hold.j = 0
2378     while True:
2379         # Select earliest extraneous event, evcode==0 if no events
2380         evcode = FSPY
2381         if game.alldone:
2382             return
2383         datemin = fintim
2384         for l in range(1, NEVENTS):
2385             if game.future[l].date < datemin:
2386                 evcode = l
2387                 if game.idebug:
2388                     prout("== Event %d fires" % evcode)
2389                 datemin = game.future[l].date
2390         xtime = datemin-game.state.date
2391         if game.iscloaked:
2392             game.energy -= xtime*500.0
2393             if game.energy <= 0:
2394                 finish(FNRG)
2395                 return
2396         game.state.date = datemin
2397         # Decrement Federation resources and recompute remaining time
2398         game.state.remres -= (game.remkl()+4*len(game.state.kcmdr))*xtime
2399         game.recompute()
2400         if game.state.remtime <= 0:
2401             finish(FDEPLETE)
2402             return
2403         # Any crew left alive?
2404         if game.state.crew <= 0:
2405             finish(FCREW)
2406             return
2407         # Is life support adequate?
2408         if damaged(DLIFSUP) and game.condition != "docked":
2409             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2410                 finish(FLIFESUP)
2411                 return
2412             game.lsupres -= xtime
2413             if game.damage[DLIFSUP] <= xtime:
2414                 game.lsupres = game.inlsr
2415         # Fix devices
2416         repair = xtime
2417         if game.condition == "docked":
2418             repair /= DOCKFAC
2419         # Don't fix Deathray here
2420         for l in range(NDEVICES):
2421             if game.damage[l] > 0.0 and l != DDRAY:
2422                 if game.damage[l]-repair > 0.0:
2423                     game.damage[l] -= repair
2424                 else:
2425                     game.damage[l] = 0.0
2426         # If radio repaired, update star chart and attack reports
2427         if radio_was_broken and not damaged(DRADIO):
2428             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2429             prout(_("   surveillance reports are coming in."))
2430             skip(1)
2431             if not game.iseenit:
2432                 attackreport(False)
2433                 game.iseenit = True
2434             rechart()
2435             prout(_("   The star chart is now up to date.\""))
2436             skip(1)
2437         # Cause extraneous event EVCODE to occur
2438         game.optime -= xtime
2439         if evcode == FSNOVA: # Supernova
2440             announce()
2441             supernova(None)
2442             schedule(FSNOVA, expran(0.5*game.intime))
2443             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2444                 return
2445         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2446             if game.state.nscrem == 0 or game.iscloaked or \
2447                 ictbeam or istract or \
2448                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2449                 return
2450             if game.ientesc or \
2451                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2452                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2453                 (damaged(DSHIELD) and \
2454                  (game.energy < 2500 or damaged(DPHASER)) and \
2455                  (game.torps < 5 or damaged(DPHOTON))):
2456                 # Tractor-beam her!
2457                 istract = ictbeam = True
2458                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2459             else:
2460                 return
2461         elif evcode == FTBEAM: # Tractor beam
2462             if not game.state.kcmdr:
2463                 unschedule(FTBEAM)
2464                 continue
2465             i = rnd.integer(len(game.state.kcmdr))
2466             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2467             if istract or game.condition == "docked" or game.iscloaked or yank == 0:
2468                 # Drats! Have to reschedule
2469                 schedule(FTBEAM,
2470                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2471                 continue
2472             ictbeam = True
2473             tractorbeam(yank)
2474         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2475             game.snapsht = copy.deepcopy(game.state)
2476             game.state.snap = True
2477             schedule(FSNAP, expran(0.5 * game.intime))
2478         elif evcode == FBATTAK: # Commander attacks starbase
2479             if not game.state.kcmdr or not game.state.baseq:
2480                 # no can do
2481                 unschedule(FBATTAK)
2482                 unschedule(FCDBAS)
2483                 continue
2484             ibq = None  # Force battle location to persist past loop
2485             try:
2486                 for ibq in game.state.baseq:
2487                     for cmdr in game.state.kcmdr:
2488                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2489                             raise JumpOut
2490                 # no match found -- try later
2491                 schedule(FBATTAK, expran(0.3*game.intime))
2492                 unschedule(FCDBAS)
2493                 continue
2494             except JumpOut:
2495                 pass
2496             # commander + starbase combination found -- launch attack
2497             game.battle = ibq
2498             schedule(FCDBAS, rnd.real(1.0, 4.0))
2499             if game.isatb: # extra time if SC already attacking
2500                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2501             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2502             game.iseenit = False
2503             if not communicating():
2504                 continue # No warning :-(
2505             game.iseenit = True
2506             announce()
2507             skip(1)
2508             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2509             prout(_("   reports that it is under attack and that it can"))
2510             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2511             if cancelrest():
2512                 return
2513         elif evcode == FSCDBAS: # Supercommander destroys base
2514             unschedule(FSCDBAS)
2515             game.isatb = 2
2516             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2517                 continue # WAS RETURN!
2518             hold = game.battle
2519             game.battle = game.state.kscmdr
2520             destroybase()
2521         elif evcode == FCDBAS: # Commander succeeds in destroying base
2522             if evcode == FCDBAS:
2523                 unschedule(FCDBAS)
2524                 if not game.state.baseq() \
2525                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2526                     game.battle.invalidate()
2527                     continue
2528                 # find the lucky pair
2529                 for cmdr in game.state.kcmdr:
2530                     if cmdr == game.battle:
2531                         break
2532                 else:
2533                     # No action to take after all
2534                     continue
2535             destroybase()
2536         elif evcode == FSCMOVE: # Supercommander moves
2537             schedule(FSCMOVE, 0.2777)
2538             if not game.ientesc and not istract and game.isatb != 1 and \
2539                    (not game.iscate or not game.justin):
2540                 supercommander()
2541         elif evcode == FDSPROB: # Move deep space probe
2542             schedule(FDSPROB, 0.01)
2543             if not game.probe.nexttok():
2544                 if not game.probe.quadrant().valid_quadrant() or \
2545                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2546                     # Left galaxy or ran into supernova
2547                     if communicating():
2548                         announce()
2549                         skip(1)
2550                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2551                         if not game.probe.quadrant().valid_quadrant():
2552                             prout(_("has left the galaxy.\""))
2553                         else:
2554                             prout(_("is no longer transmitting.\""))
2555                     unschedule(FDSPROB)
2556                     continue
2557                 if communicating():
2558                     #announce()
2559                     skip(1)
2560                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2561             pquad = game.probe.quadrant()
2562             pdest = game.state.galaxy[pquad.i][pquad.j]
2563             if communicating():
2564                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2565                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2566                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2567                 pdest.charted = True
2568             game.probe.moves -= 1 # One less to travel
2569             if game.probe.arrived() and game.isarmed and pdest.stars:
2570                 supernova(game.probe)                # fire in the hole!
2571                 unschedule(FDSPROB)
2572                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2573                     return
2574         elif evcode == FDISTR: # inhabited system issues distress call
2575             unschedule(FDISTR)
2576             # try a whole bunch of times to find something suitable
2577             for i in range(100):
2578                 # need a quadrant which is not the current one,
2579                 # which has some stars which are inhabited and
2580                 # not already under attack, which is not
2581                 # supernova'ed, and which has some Klingons in it
2582                 w = randplace(GALSIZE)
2583                 q = game.state.galaxy[w.i][w.j]
2584                 if not (game.quadrant == w or q.planet is None or \
2585                       not q.planet.inhabited or \
2586                       q.supernova or q.status!="secure" or q.klingons<=0):
2587                     break
2588             else:
2589                 # can't seem to find one; ignore this call
2590                 if game.idebug:
2591                     prout("=== Couldn't find location for distress event.")
2592                 continue
2593             # got one!!  Schedule its enslavement
2594             ev = schedule(FENSLV, expran(game.intime))
2595             ev.quadrant = w
2596             q.status = "distressed"
2597             # tell the captain about it if we can
2598             if communicating():
2599                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2600                         % (q.planet, repr(w)))
2601                 prout(_("by a Klingon invasion fleet."))
2602                 if cancelrest():
2603                     return
2604         elif evcode == FENSLV:                # starsystem is enslaved
2605             ev = unschedule(FENSLV)
2606             # see if current distress call still active
2607             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2608             if q.klingons <= 0:
2609                 q.status = "secure"
2610                 continue
2611             q.status = "enslaved"
2612
2613             # play stork and schedule the first baby
2614             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2615             ev2.quadrant = ev.quadrant
2616
2617             # report the disaster if we can
2618             if communicating():
2619                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2620                         q.planet)
2621                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2622         elif evcode == FREPRO:                # Klingon reproduces
2623             # If we ever switch to a real event queue, we'll need to
2624             # explicitly retrieve and restore the x and y.
2625             ev = schedule(FREPRO, expran(1.0 * game.intime))
2626             # see if current distress call still active
2627             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2628             if q.klingons <= 0:
2629                 q.status = "secure"
2630                 continue
2631             if game.remkl() >= MAXKLGAME:
2632                 continue                # full right now
2633             # reproduce one Klingon
2634             w = ev.quadrant
2635             m = Coord()
2636             if game.klhere >= MAXKLQUAD:
2637                 try:
2638                     # this quadrant not ok, pick an adjacent one
2639                     for m.i in range(w.i - 1, w.i + 2):
2640                         for m.j in range(w.j - 1, w.j + 2):
2641                             if not m.valid_quadrant():
2642                                 continue
2643                             q = game.state.galaxy[m.i][m.j]
2644                             # check for this quad ok (not full & no snova)
2645                             if q.klingons >= MAXKLQUAD or q.supernova:
2646                                 continue
2647                             raise JumpOut
2648                     # search for eligible quadrant failed
2649                     continue
2650                 except JumpOut:
2651                     w = m
2652             # deliver the child
2653             q.klingons += 1
2654             if game.quadrant == w:
2655                 game.klhere += 1
2656                 newkling() # also adds it to game.enemies
2657             # recompute time left
2658             game.recompute()
2659             if communicating():
2660                 if game.quadrant == w:
2661                     prout(_("Spock- sensors indicate the Klingons have"))
2662                     prout(_("launched a warship from %s.") % q.planet)
2663                 else:
2664                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2665                     if q.planet is not None:
2666                         proutn(_("near %s ") % q.planet)
2667                     prout(_("in Quadrant %s.") % w)
2668
2669 def wait():
2670     "Wait on events."
2671     game.ididit = False
2672     while True:
2673         key = scanner.nexttok()
2674         if key  != "IHEOL":
2675             break
2676         proutn(_("How long? "))
2677         scanner.chew()
2678     if key != "IHREAL":
2679         huh()
2680         return
2681     origTime = delay = scanner.real
2682     if delay <= 0.0:
2683         return
2684     if delay >= game.state.remtime or len(game.enemies) != 0:
2685         proutn(_("Are you sure? "))
2686         if not ja():
2687             return
2688     # Alternate resting periods (events) with attacks
2689     game.resting = True
2690     while True:
2691         if delay <= 0:
2692             game.resting = False
2693         if not game.resting:
2694             prout(_("%d stardates left.") % int(game.state.remtime))
2695             return
2696         temp = game.optime = delay
2697         if len(game.enemies):
2698             rtime = rnd.real(1.0, 2.0)
2699             if rtime < temp:
2700                 temp = rtime
2701             game.optime = temp
2702         if game.optime < delay:
2703             attack(torps_ok=False)
2704         if game.alldone:
2705             return
2706         events()
2707         game.ididit = True
2708         if game.alldone:
2709             return
2710         delay -= temp
2711         # Repair Deathray if long rest at starbase
2712         if origTime-delay >= 9.99 and game.condition == "docked":
2713             game.damage[DDRAY] = 0.0
2714         # leave if quadrant supernovas
2715         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2716             break
2717     game.resting = False
2718     game.optime = 0.0
2719
2720 def nova(nov):
2721     "Star goes nova."
2722     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2723     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2724     if rnd.withprob(0.05):
2725         # Wow! We've supernova'ed
2726         supernova(game.quadrant)
2727         return
2728     # handle initial nova
2729     game.quad[nov.i][nov.j] = '.'
2730     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2731     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2732     game.state.starkl += 1
2733     # Set up queue to recursively trigger adjacent stars
2734     hits = [nov]
2735     kount = 0
2736     while hits:
2737         offset = Coord()
2738         start = hits.pop()
2739         for offset.i in range(-1, 1+1):
2740             for offset.j in range(-1, 1+1):
2741                 if offset.j == 0 and offset.i == 0:
2742                     continue
2743                 neighbor = start + offset
2744                 if not neighbor.valid_sector():
2745                     continue
2746                 iquad = game.quad[neighbor.i][neighbor.j]
2747                 # Empty space ends reaction
2748                 if iquad in ('.', '?', ' ', 'T', '#'):
2749                     pass
2750                 elif iquad == '*': # Affect another star
2751                     if rnd.withprob(0.05):
2752                         # This star supernovas
2753                         supernova(game.quadrant)
2754                         return
2755                     else:
2756                         hits.append(neighbor)
2757                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2758                         game.state.starkl += 1
2759                         proutn(crmena(True, '*', "sector", neighbor))
2760                         prout(_(" novas."))
2761                         game.quad[neighbor.i][neighbor.j] = '.'
2762                         kount += 1
2763                 elif iquad in ('P', '@'): # Destroy planet
2764                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2765                     if iquad == 'P':
2766                         game.state.nplankl += 1
2767                     else:
2768                         game.state.nworldkl += 1
2769                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2770                     game.iplnet.pclass = "destroyed"
2771                     game.iplnet = None
2772                     game.plnet.invalidate()
2773                     if game.landed:
2774                         finish(FPNOVA)
2775                         return
2776                     game.quad[neighbor.i][neighbor.j] = '.'
2777                 elif iquad == 'B': # Destroy base
2778                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2779                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2780                     game.base.invalidate()
2781                     game.state.basekl += 1
2782                     newcnd()
2783                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2784                     game.quad[neighbor.i][neighbor.j] = '.'
2785                 elif iquad in ('E', 'F'): # Buffet ship
2786                     prout(_("***Starship buffeted by nova."))
2787                     if game.shldup:
2788                         if game.shield >= 2000.0:
2789                             game.shield -= 2000.0
2790                         else:
2791                             diff = 2000.0 - game.shield
2792                             game.energy -= diff
2793                             game.shield = 0.0
2794                             game.shldup = False
2795                             prout(_("***Shields knocked out."))
2796                             game.damage[DSHIELD] += 0.005*game.damfac*rnd.real()*diff
2797                     else:
2798                         game.energy -= 2000.0
2799                     if game.energy <= 0:
2800                         finish(FNOVA)
2801                         return
2802                     # add in course nova contributes to kicking starship
2803                     if hits:
2804                         bump += (game.sector-hits[-1]).sgn()
2805                 elif iquad == 'K': # kill klingon
2806                     deadkl(neighbor, iquad, neighbor)
2807                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2808                     target = None
2809                     for ll in range(len(game.enemies)):
2810                         if game.enemies[ll].location == neighbor:
2811                             target = game.enemies[ll]
2812                             break
2813                     if target is not None:
2814                         target.power -= 800.0 # If firepower is lost, die
2815                         if target.power <= 0.0:
2816                             deadkl(neighbor, iquad, neighbor)
2817                             continue    # neighbor loop
2818                         # Else enemy gets flung by the blast wave
2819                         newc = neighbor + neighbor - start
2820                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2821                         if not newc.valid_sector():
2822                             # can't leave quadrant
2823                             skip(1)
2824                             continue
2825                         iquad1 = game.quad[newc.i][newc.j]
2826                         if iquad1 == ' ':
2827                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2828                             skip(1)
2829                             deadkl(neighbor, iquad, newc)
2830                             continue
2831                         if iquad1 != '.':
2832                             # can't move into something else
2833                             skip(1)
2834                             continue
2835                         proutn(_(", buffeted to Sector %s") % newc)
2836                         game.quad[neighbor.i][neighbor.j] = '.'
2837                         game.quad[newc.i][newc.j] = iquad
2838                         target.move(newc)
2839     # Starship affected by nova -- kick it away.
2840     dist = kount*0.1
2841     direc = ncourse[3*(bump.i+1)+bump.j+2]
2842     if direc == 0.0:
2843         dist = 0.0
2844     if dist == 0.0:
2845         return
2846     scourse = course(bearing=direc, distance=dist)
2847     game.optime = scourse.time(w=4)
2848     skip(1)
2849     prout(_("Force of nova displaces starship."))
2850     imove(scourse, noattack=True)
2851     game.optime = scourse.time(w=4)
2852     return
2853
2854 def supernova(w):
2855     "Star goes supernova."
2856     num = 0; npdead = 0
2857     if w is not None:
2858         nq = copy.copy(w)
2859     else:
2860         # Scheduled supernova -- select star at random.
2861         nstars = 0
2862         nq = Coord()
2863         for nq.i in range(GALSIZE):
2864             for nq.j in range(GALSIZE):
2865                 nstars += game.state.galaxy[nq.i][nq.j].stars
2866         if nstars == 0:
2867             return # nothing to supernova exists
2868         num = rnd.integer(nstars) + 1
2869         for nq.i in range(GALSIZE):
2870             for nq.j in range(GALSIZE):
2871                 num -= game.state.galaxy[nq.i][nq.j].stars
2872                 if num <= 0:
2873                     break
2874             if num <=0:
2875                 break
2876         if game.idebug:
2877             proutn("=== Super nova here?")
2878             if ja():
2879                 nq = game.quadrant
2880     if nq != game.quadrant or game.justin:
2881         # it isn't here, or we just entered (treat as enroute)
2882         if communicating():
2883             skip(1)
2884             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2885             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2886     else:
2887         ns = Coord()
2888         # we are in the quadrant!
2889         num = rnd.integer(game.state.galaxy[nq.i][nq.j].stars) + 1
2890         for ns.i in range(QUADSIZE):
2891             for ns.j in range(QUADSIZE):
2892                 if game.quad[ns.i][ns.j]=='*':
2893                     num -= 1
2894                     if num==0:
2895                         break
2896             if num==0:
2897                 break
2898         skip(1)
2899         prouts(_("***RED ALERT!  RED ALERT!"))
2900         skip(1)
2901         prout(_("***Incipient supernova detected at Sector %s") % ns)
2902         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2903             proutn(_("Emergency override attempts t"))
2904             prouts("***************")
2905             skip(1)
2906             stars()
2907             game.alldone = True
2908     # destroy any Klingons in supernovaed quadrant
2909     game.state.galaxy[nq.i][nq.j].klingons = 0
2910     if nq == game.state.kscmdr:
2911         # did in the Supercommander!
2912         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2913         game.iscate = False
2914         unschedule(FSCMOVE)
2915         unschedule(FSCDBAS)
2916     # Changing this to [w for w in game.state.kcmdr if w != nq]
2917     # causes regression-test failure
2918     survivors = list(filter(lambda w: w != nq, game.state.kcmdr))
2919     #comkills = len(game.state.kcmdr) - len(survivors)
2920     game.state.kcmdr = survivors
2921     if not game.state.kcmdr:
2922         unschedule(FTBEAM)
2923     # destroy Romulans and planets in supernovaed quadrant
2924     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2925     game.state.galaxy[nq.i][nq.j].romulans = 0
2926     game.state.nromrem -= nrmdead
2927     # Destroy planets
2928     for loop in range(game.inplan):
2929         if game.state.planets[loop].quadrant == nq:
2930             game.state.planets[loop].pclass = "destroyed"
2931             npdead += 1
2932     # Destroy any base in supernovaed quadrant
2933     game.state.baseq = [x for x in game.state.baseq if x != nq]
2934     # If starship caused supernova, tally up destruction
2935     if w is not None:
2936         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2937         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2938         game.state.nplankl += npdead
2939     # mark supernova in galaxy and in star chart
2940     if game.quadrant == nq or communicating():
2941         game.state.galaxy[nq.i][nq.j].supernova = True
2942     # If supernova destroys last Klingons give special message
2943     if game.unwon()==0 and nq != game.quadrant:
2944         skip(2)
2945         if w is None:
2946             prout(_("Lucky you!"))
2947         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2948         finish(FWON)
2949         return
2950     # if some Klingons remain, continue or die in supernova
2951     if game.alldone:
2952         finish(FSNOVAED)
2953     return
2954
2955 # Code from finish.c ends here.
2956
2957 def selfdestruct():
2958     "Self-destruct maneuver. Finish with a BANG!"
2959     scanner.chew()
2960     if damaged(DCOMPTR):
2961         prout(_("Computer damaged; cannot execute destruct sequence."))
2962         return
2963     prouts(_("---WORKING---")); skip(1)
2964     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2965     prouts("   10"); skip(1)
2966     prouts("       9"); skip(1)
2967     prouts("          8"); skip(1)
2968     prouts("             7"); skip(1)
2969     prouts("                6"); skip(1)
2970     skip(1)
2971     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2972     skip(1)
2973     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2974     skip(1)
2975     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2976     skip(1)
2977     scanner.nexttok()
2978     if game.passwd != scanner.token:
2979         prouts(_("PASSWORD-REJECTED;"))
2980         skip(1)
2981         prouts(_("CONTINUITY-EFFECTED"))
2982         skip(2)
2983         return
2984     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2985     prouts("                   5"); skip(1)
2986     prouts("                      4"); skip(1)
2987     prouts("                         3"); skip(1)
2988     prouts("                            2"); skip(1)
2989     prouts("                              1"); skip(1)
2990     if rnd.withprob(0.15):
2991         prouts(_("GOODBYE-CRUEL-WORLD"))
2992         skip(1)
2993     kaboom()
2994
2995 def kaboom():
2996     stars()
2997     if game.ship=='E':
2998         prouts("***")
2999     prouts(_("********* Entropy of %s maximized *********") % crmshp())
3000     skip(1)
3001     stars()
3002     skip(1)
3003     if len(game.enemies) != 0:
3004         whammo = 25.0 * game.energy
3005         for e in game.enemies[::-1]:
3006             if e.power*e.kdist <= whammo:
3007                 deadkl(e.location, game.quad[e.location.i][e.location.j], e.location)
3008     finish(FDILITHIUM)
3009
3010 def killrate():
3011     "Compute our rate of kils over time."
3012     elapsed = game.state.date - game.indate
3013     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
3014         return 0
3015     else:
3016         starting = (game.inkling + game.incom + game.inscom)
3017         remaining = game.unwon()
3018         return (starting - remaining)/elapsed
3019
3020 def badpoints():
3021     "Compute demerits."
3022     badpt = 5.0*game.state.starkl + \
3023             game.casual + \
3024             10.0*game.state.nplankl + \
3025             300*game.state.nworldkl + \
3026             45.0*game.nhelp +\
3027             100.0*game.state.basekl +\
3028             3.0*game.abandoned +\
3029             100*game.ncviol
3030     if game.ship == 'F':
3031         badpt += 100.0
3032     elif game.ship is None:
3033         badpt += 200.0
3034     return badpt
3035
3036 def finish(ifin):
3037     # end the game, with appropriate notifications
3038     igotit = False
3039     game.alldone = True
3040     skip(3)
3041     prout(_("It is stardate %.1f.") % game.state.date)
3042     skip(1)
3043     if ifin == FWON: # Game has been won
3044         if game.state.nromrem != 0:
3045             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
3046                   game.state.nromrem)
3047
3048         prout(_("You have smashed the Klingon invasion fleet and saved"))
3049         prout(_("the Federation."))
3050         if game.alive and game.brigcapacity-game.brigfree > 0:
3051             game.kcaptured += game.brigcapacity-game.brigfree
3052             prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
3053         game.gamewon = True
3054         if game.alive:
3055             badpt = badpoints()
3056             if badpt < 100.0:
3057                 badpt = 0.0        # Close enough!
3058             # killsPerDate >= RateMax
3059             if game.state.date-game.indate < 5.0 or \
3060                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
3061                 skip(1)
3062                 prout(_("In fact, you have done so well that Starfleet Command"))
3063                 if game.skill == SKILL_NOVICE:
3064                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
3065                 elif game.skill == SKILL_FAIR:
3066                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
3067                 elif game.skill == SKILL_GOOD:
3068                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
3069                 elif game.skill == SKILL_EXPERT:
3070                     prout(_("promotes you to Commodore Emeritus."))
3071                     skip(1)
3072                     prout(_("Now that you think you're really good, try playing"))
3073                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
3074                 elif game.skill == SKILL_EMERITUS:
3075                     skip(1)
3076                     proutn(_("Computer-  "))
3077                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
3078                     skip(2)
3079                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
3080                     skip(1)
3081                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3082                     skip(1)
3083                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3084                     skip(1)
3085                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3086                     skip(1)
3087                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
3088                     skip(2)
3089                     prout(_("Now you can retire and write your own Star Trek game!"))
3090                     skip(1)
3091                 elif game.skill >= SKILL_EXPERT:
3092                     if game.thawed and not game.idebug:
3093                         prout(_("You cannot get a citation, so..."))
3094                     else:
3095                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3096                         scanner.chew()
3097                         if ja():
3098                             igotit = True
3099             # Only grant long life if alive (original didn't!)
3100             skip(1)
3101             prout(_("LIVE LONG AND PROSPER."))
3102         score()
3103         if igotit:
3104             plaque()
3105         return
3106     elif ifin == FDEPLETE: # Federation Resources Depleted
3107         prout(_("Your time has run out and the Federation has been"))
3108         prout(_("conquered.  Your starship is now Klingon property,"))
3109         prout(_("and you are put on trial as a war criminal.  On the"))
3110         proutn(_("basis of your record, you are "))
3111         if game.unwon()*3.0 > (game.inkling + game.incom + game.inscom):
3112             prout(_("acquitted."))
3113             skip(1)
3114             prout(_("LIVE LONG AND PROSPER."))
3115         else:
3116             prout(_("found guilty and"))
3117             prout(_("sentenced to death by slow torture."))
3118             game.alive = False
3119         score()
3120         return
3121     elif ifin == FLIFESUP:
3122         prout(_("Your life support reserves have run out, and"))
3123         prout(_("you die of thirst, starvation, and asphyxiation."))
3124         prout(_("Your starship is a derelict in space."))
3125     elif ifin == FNRG:
3126         prout(_("Your energy supply is exhausted."))
3127         skip(1)
3128         prout(_("Your starship is a derelict in space."))
3129     elif ifin == FBATTLE:
3130         prout(_("The %s has been destroyed in battle.") % crmshp())
3131         skip(1)
3132         prout(_("Dulce et decorum est pro patria mori."))
3133     elif ifin == FNEG3:
3134         prout(_("You have made three attempts to cross the negative energy"))
3135         prout(_("barrier which surrounds the galaxy."))
3136         skip(1)
3137         prout(_("Your navigation is abominable."))
3138         score()
3139     elif ifin == FNOVA:
3140         prout(_("Your starship has been destroyed by a nova."))
3141         prout(_("That was a great shot."))
3142         skip(1)
3143     elif ifin == FSNOVAED:
3144         prout(_("The %s has been fried by a supernova.") % crmshp())
3145         prout(_("...Not even cinders remain..."))
3146     elif ifin == FABANDN:
3147         prout(_("You have been captured by the Klingons. If you still"))
3148         prout(_("had a starbase to be returned to, you would have been"))
3149         prout(_("repatriated and given another chance. Since you have"))
3150         prout(_("no starbases, you will be mercilessly tortured to death."))
3151     elif ifin == FDILITHIUM:
3152         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3153     elif ifin == FMATERIALIZE:
3154         prout(_("Starbase was unable to re-materialize your starship."))
3155         prout(_("Sic transit gloria mundi"))
3156     elif ifin == FPHASER:
3157         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3158     elif ifin == FLOST:
3159         prout(_("You and your landing party have been"))
3160         prout(_("converted to energy, dissipating through space."))
3161     elif ifin == FMINING:
3162         prout(_("You are left with your landing party on"))
3163         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3164         skip(1)
3165         prout(_("They are very fond of \"Captain Kirk\" soup."))
3166         skip(1)
3167         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3168     elif ifin == FDPLANET:
3169         prout(_("You and your mining party perish."))
3170         skip(1)
3171         prout(_("That was a great shot."))
3172         skip(1)
3173     elif ifin == FSSC:
3174         prout(_("The Galileo is instantly annihilated by the supernova."))
3175         prout(_("You and your mining party are atomized."))
3176         skip(1)
3177         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3178         prout(_("joins the Romulans, wreaking terror on the Federation."))
3179     elif ifin == FPNOVA:
3180         prout(_("You and your mining party are atomized."))
3181         skip(1)
3182         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3183         prout(_("joins the Romulans, wreaking terror on the Federation."))
3184     elif ifin == FSTRACTOR:
3185         prout(_("The shuttle craft Galileo is also caught,"))
3186         prout(_("and breaks up under the strain."))
3187         skip(1)
3188         prout(_("Your debris is scattered for millions of miles."))
3189         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3190     elif ifin == FDRAY:
3191         prout(_("The mutants attack and kill Spock."))
3192         prout(_("Your ship is captured by Klingons, and"))
3193         prout(_("your crew is put on display in a Klingon zoo."))
3194     elif ifin == FTRIBBLE:
3195         prout(_("Tribbles consume all remaining water,"))
3196         prout(_("food, and oxygen on your ship."))
3197         skip(1)
3198         prout(_("You die of thirst, starvation, and asphyxiation."))
3199         prout(_("Your starship is a derelict in space."))
3200     elif ifin == FHOLE:
3201         prout(_("Your ship is drawn to the center of the black hole."))
3202         prout(_("You are crushed into extremely dense matter."))
3203     elif ifin == FCLOAK:
3204         game.ncviol += 1
3205         prout(_("You have violated the Treaty of Algeron."))
3206         prout(_("The Romulan Empire can never trust you again."))
3207     elif ifin == FCREW:
3208         prout(_("Your last crew member has died."))
3209     if ifin != FWON and ifin != FCLOAK and game.iscloaked:
3210         prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
3211         prout(_("You may have missed some warning messages."))
3212         skip(1)
3213     if game.ship == 'F':
3214         game.ship = None
3215     elif game.ship == 'E':
3216         game.ship = 'F'
3217     game.alive = False
3218     if game.unwon() != 0:
3219         goodies = game.state.remres/game.inresor
3220         baddies = (game.remkl() + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3221         if goodies/baddies >= rnd.real(1.0, 1.5):
3222             prout(_("As a result of your actions, a treaty with the Klingon"))
3223             prout(_("Empire has been signed. The terms of the treaty are"))
3224             if goodies/baddies >= rnd.real(3.0):
3225                 prout(_("favorable to the Federation."))
3226                 skip(1)
3227                 prout(_("Congratulations!"))
3228             else:
3229                 prout(_("highly unfavorable to the Federation."))
3230         else:
3231             prout(_("The Federation will be destroyed."))
3232     else:
3233         prout(_("Since you took the last Klingon with you, you are a"))
3234         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3235         prout(_("statue in your memory. Rest in peace, and try not"))
3236         prout(_("to think about pigeons."))
3237         game.gamewon = True
3238     score()
3239     scanner.chew()      # Clean up leftovers
3240
3241 def score():
3242     "Compute player's score."
3243     timused = game.state.date - game.indate
3244     if (timused == 0 or game.unwon() != 0) and timused < 5.0:
3245         timused = 5.0
3246     game.perdate = killrate()
3247     ithperd = 500*game.perdate + 0.5
3248     iwon = 0
3249     if game.gamewon:
3250         iwon = 100*game.skill
3251     if game.ship == 'E':
3252         klship = 0
3253     elif game.ship == 'F':
3254         klship = 1
3255     else:
3256         klship = 2
3257     dead_ordinaries= game.inkling - game.remkl() + len(game.state.kcmdr) + game.state.nscrem
3258     game.score = 10*(dead_ordinaries)\
3259              + 50*(game.incom - len(game.state.kcmdr)) \
3260              + ithperd + iwon \
3261              + 20*(game.inrom - game.state.nromrem) \
3262              + 200*(game.inscom - game.state.nscrem) \
3263                  - game.state.nromrem \
3264              + 3 * game.kcaptured \
3265              - badpoints()
3266     if not game.alive:
3267         game.score -= 200
3268     skip(2)
3269     prout(_("Your score --"))
3270     if game.inrom - game.state.nromrem:
3271         prout(_("%6d Romulans destroyed                 %5d") %
3272               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3273     if game.state.nromrem and game.gamewon:
3274         prout(_("%6d Romulans captured                  %5d") %
3275               (game.state.nromrem, game.state.nromrem))
3276     if dead_ordinaries:
3277         prout(_("%6d ordinary Klingons destroyed        %5d") %
3278               (dead_ordinaries, 10*dead_ordinaries))
3279     if game.incom - len(game.state.kcmdr):
3280         prout(_("%6d Klingon commanders destroyed       %5d") %
3281               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3282     if game.kcaptured:
3283         prout(_("%d Klingons captured                   %5d") %
3284               (game.kcaptured, 3 * game.kcaptured))
3285     if game.inscom - game.state.nscrem:
3286         prout(_("%6d Super-Commander destroyed          %5d") %
3287               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3288     if ithperd:
3289         prout(_("%6.2f Klingons per stardate              %5d") %
3290               (game.perdate, ithperd))
3291     if game.state.starkl:
3292         prout(_("%6d stars destroyed by your action     %5d") %
3293               (game.state.starkl, -5*game.state.starkl))
3294     if game.state.nplankl:
3295         prout(_("%6d planets destroyed by your action   %5d") %
3296               (game.state.nplankl, -10*game.state.nplankl))
3297     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3298         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3299               (game.state.nworldkl, -300*game.state.nworldkl))
3300     if game.state.basekl:
3301         prout(_("%6d bases destroyed by your action     %5d") %
3302               (game.state.basekl, -100*game.state.basekl))
3303     if game.nhelp:
3304         prout(_("%6d calls for help from starbase       %5d") %
3305               (game.nhelp, -45*game.nhelp))
3306     if game.casual:
3307         prout(_("%6d casualties incurred                %5d") %
3308               (game.casual, -game.casual))
3309     if game.abandoned:
3310         prout(_("%6d crew abandoned in space            %5d") %
3311               (game.abandoned, -3*game.abandoned))
3312     if klship:
3313         prout(_("%6d ship(s) lost or destroyed          %5d") %
3314               (klship, -100*klship))
3315     if game.ncviol > 0:
3316         if game.ncviol == 1:
3317             prout(_("1 Treaty of Algeron violation          -100"))
3318         else:
3319             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3320                   (game.ncviol, -100*game.ncviol))
3321     if not game.alive:
3322         prout(_("Penalty for getting yourself killed        -200"))
3323     if game.gamewon:
3324         proutn(_("Bonus for winning "))
3325         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3326         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3327         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3328         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3329         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3330         prout("           %5d" % iwon)
3331     skip(1)
3332     prout(_("TOTAL SCORE                               %5d") % game.score)
3333
3334 def plaque():
3335     "Emit winner's commemmorative plaque."
3336     skip(2)
3337     while True:
3338         proutn(_("File or device name for your plaque: "))
3339         winner = cgetline()
3340         try:
3341             fp = open(winner, "w")
3342             break
3343         except IOError:
3344             prout(_("Invalid name."))
3345
3346     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3347     winner = cgetline()
3348     # The 38 below must be 64 for 132-column paper
3349     nskip = 38 - len(winner)/2
3350     # This is where the ASCII art picture was emitted.
3351     # It got garbled somewhere in the chain of transmission to the Almy version.
3352     # We should restore it if we can find old enough FORTRAN sources.
3353     fp.write("\n\n\n")
3354     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3355     fp.write("\n\n\n\n")
3356     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3357     fp.write("\n")
3358     fp.write(_("                                                Starfleet Command bestows to you\n"))
3359     fp.write("\n")
3360     fp.write("%*s%s\n\n" % (nskip, "", winner))
3361     fp.write(_("                                                           the rank of\n\n"))
3362     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3363     fp.write("                                                          ")
3364     if game.skill ==  SKILL_EXPERT:
3365         fp.write(_(" Expert level\n\n"))
3366     elif game.skill == SKILL_EMERITUS:
3367         fp.write(_("Emeritus level\n\n"))
3368     else:
3369         fp.write(_(" Cheat level\n\n"))
3370     timestring = time.ctime()
3371     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3372              (timestring+4, timestring+20, timestring+11))
3373     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3374     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3375     fp.close()
3376
3377 # Code from io.c begins here
3378
3379 rows = linecount = 0        # for paging
3380 stdscr = None
3381 replayfp = None
3382 fullscreen_window = None
3383 srscan_window     = None   # Short range scan
3384 report_window     = None   # Report legends for status window
3385 status_window     = None   # The status window itself
3386 lrscan_window     = None   # Long range scan
3387 message_window    = None   # Main window for scrolling text
3388 prompt_window     = None   # Prompt window at bottom of display
3389 curwnd = None
3390
3391 def iostart():
3392     global stdscr, rows
3393     # for some recent versions of python2, the following enables UTF8
3394     # for the older ones we probably need to set C locale, and python3
3395     # has no problems at all
3396     if sys.version_info[0] < 3:
3397         locale.setlocale(locale.LC_ALL, "")
3398     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3399     gettext.textdomain("sst")
3400     if not (game.options & OPTION_CURSES):
3401         ln_env = os.getenv("LINES")
3402         if ln_env:
3403             rows = ln_env
3404         else:
3405             rows = 25
3406     else:
3407         stdscr = curses.initscr()
3408         stdscr.keypad(True)
3409         curses.nonl()
3410         curses.cbreak()
3411         if game.options & OPTION_COLOR:
3412             curses.start_color()
3413             curses.use_default_colors()
3414             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3415             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3416             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3417             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3418             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3419             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3420             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3421             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3422         global fullscreen_window, srscan_window, report_window, status_window
3423         global lrscan_window, message_window, prompt_window
3424         (rows, _columns)   = stdscr.getmaxyx()
3425         fullscreen_window = stdscr
3426         srscan_window     = curses.newwin(12, 25, 0,       0)
3427         report_window     = curses.newwin(11, 0,  1,       25)
3428         status_window     = curses.newwin(10, 0,  1,       39)
3429         lrscan_window     = curses.newwin(5,  0,  0,       64)
3430         message_window    = curses.newwin(0,  0,  12,      0)
3431         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3432         message_window.scrollok(True)
3433         setwnd(fullscreen_window)
3434
3435 def ioend():
3436     "Wrap up I/O."
3437     if game.options & OPTION_CURSES:
3438         stdscr.keypad(False)
3439         curses.echo()
3440         curses.nocbreak()
3441         curses.endwin()
3442
3443 def waitfor():
3444     "Wait for user action -- OK to do nothing if on a TTY"
3445     if game.options & OPTION_CURSES:
3446         stdscr.getch()
3447
3448 def announce():
3449     skip(1)
3450     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3451     skip(1)
3452
3453 def pause_game():
3454     if game.skill > SKILL_FAIR:
3455         prompt = _("[CONTINUE?]")
3456     else:
3457         prompt = _("[PRESS ENTER TO CONTINUE]")
3458
3459     if game.options & OPTION_CURSES:
3460         drawmaps(0)
3461         setwnd(prompt_window)
3462         prompt_window.clear()
3463         prompt_window.addstr(prompt)
3464         prompt_window.getstr()
3465         prompt_window.clear()
3466         prompt_window.refresh()
3467         setwnd(message_window)
3468     else:
3469         global linecount
3470         sys.stdout.write('\n')
3471         proutn(prompt)
3472         if not replayfp:
3473             my_input()
3474         sys.stdout.write('\n' * rows)
3475         linecount = 0
3476
3477 def skip(i):
3478     "Skip i lines.  Pause game if this would cause a scrolling event."
3479     for _dummy in range(i):
3480         if game.options & OPTION_CURSES:
3481             (y, _x) = curwnd.getyx()
3482             try:
3483                 curwnd.move(y+1, 0)
3484             except curses.error:
3485                 pass
3486         else:
3487             global linecount
3488             linecount += 1
3489             if rows and linecount >= rows:
3490                 pause_game()
3491             else:
3492                 sys.stdout.write('\n')
3493
3494 def proutn(proutntline):
3495     "Utter a line with no following line feed."
3496     if game.options & OPTION_CURSES:
3497         (y, x) = curwnd.getyx()
3498         (my, _mx) = curwnd.getmaxyx()
3499         if curwnd == message_window and y >= my - 2:
3500             pause_game()
3501             clrscr()
3502         if logfp and game.cdebug:
3503             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3504         curwnd.addstr(proutntline)
3505         curwnd.refresh()
3506     else:
3507         sys.stdout.write(proutntline)
3508         sys.stdout.flush()
3509
3510 def prout(proutline):
3511     proutn(proutline)
3512     skip(1)
3513
3514 def prouts(proutsline):
3515     "Emit slowly!"
3516     for c in proutsline:
3517         if not replayfp or replayfp.closed:        # Don't slow down replays
3518             time.sleep(0.03)
3519         proutn(c)
3520         if game.options & OPTION_CURSES:
3521             curwnd.refresh()
3522         else:
3523             sys.stdout.flush()
3524     if not replayfp or replayfp.closed:
3525         time.sleep(0.03)
3526
3527 def cgetline():
3528     "Get a line of input."
3529     if game.options & OPTION_CURSES:
3530         linein = codecs.decode(curwnd.getstr()) + "\n"
3531         curwnd.refresh()
3532     else:
3533         if replayfp and not replayfp.closed:
3534             while True:
3535                 linein = replayfp.readline()
3536                 proutn(linein)
3537                 if linein == '':
3538                     prout("*** Replay finished")
3539                     replayfp.close()
3540                     break
3541                 elif linein[0] != "#":
3542                     break
3543         else:
3544             try:
3545                 linein = my_input() + "\n"
3546             except EOFError:
3547                 prout("")
3548                 sys.exit(0)
3549     if logfp:
3550         logfp.write(linein)
3551     return linein
3552
3553 def setwnd(wnd):
3554     "Change windows -- OK for this to be a no-op in tty mode."
3555     global curwnd
3556     if game.options & OPTION_CURSES:
3557         if game.cdebug and logfp:
3558             if wnd == fullscreen_window:
3559                 legend = "fullscreen"
3560             elif wnd == srscan_window:
3561                 legend = "srscan"
3562             elif wnd == report_window:
3563                 legend = "report"
3564             elif wnd == status_window:
3565                 legend = "status"
3566             elif wnd == lrscan_window:
3567                 legend = "lrscan"
3568             elif wnd == message_window:
3569                 legend = "message"
3570             elif wnd == prompt_window:
3571                 legend = "prompt"
3572             else:
3573                 legend = "unknown"
3574             logfp.write("#curses: setwnd(%s)\n" % legend)
3575         curwnd = wnd
3576         # Some curses implementations get confused when you try this.
3577         try:
3578             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3579         except curses.error:
3580             pass
3581
3582 def clreol():
3583     "Clear to end of line -- can be a no-op in tty mode"
3584     if game.options & OPTION_CURSES:
3585         curwnd.clrtoeol()
3586         curwnd.refresh()
3587
3588 def clrscr():
3589     "Clear screen -- can be a no-op in tty mode."
3590     global linecount
3591     if game.options & OPTION_CURSES:
3592         curwnd.clear()
3593         curwnd.move(0, 0)
3594         curwnd.refresh()
3595     linecount = 0
3596
3597 def textcolor(color=DEFAULT):
3598     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3599         if color == DEFAULT:
3600             curwnd.attrset(0)
3601         elif color ==  BLACK:
3602             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3603         elif color ==  BLUE:
3604             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3605         elif color ==  GREEN:
3606             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3607         elif color ==  CYAN:
3608             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3609         elif color ==  RED:
3610             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3611         elif color ==  MAGENTA:
3612             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3613         elif color ==  BROWN:
3614             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3615         elif color ==  LIGHTGRAY:
3616             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3617         elif color ==  DARKGRAY:
3618             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3619         elif color ==  LIGHTBLUE:
3620             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3621         elif color ==  LIGHTGREEN:
3622             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3623         elif color ==  LIGHTCYAN:
3624             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3625         elif color ==  LIGHTRED:
3626             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3627         elif color ==  LIGHTMAGENTA:
3628             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3629         elif color ==  YELLOW:
3630             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3631         elif color ==  WHITE:
3632             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3633
3634 def highvideo():
3635     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3636         curwnd.attron(curses.A_REVERSE)
3637
3638 #
3639 # Things past this point have policy implications.
3640 #
3641
3642 def drawmaps(mode):
3643     "Hook to be called after moving to redraw maps."
3644     if game.options & OPTION_CURSES:
3645         if mode == 1:
3646             sensor()
3647         setwnd(srscan_window)
3648         curwnd.move(0, 0)
3649         srscan()
3650         if mode != 2:
3651             setwnd(status_window)
3652             status_window.clear()
3653             status_window.move(0, 0)
3654             setwnd(report_window)
3655             report_window.clear()
3656             report_window.move(0, 0)
3657             status()
3658             setwnd(lrscan_window)
3659             lrscan_window.clear()
3660             lrscan_window.move(0, 0)
3661             lrscan(silent=False)
3662
3663 def put_srscan_sym(w, sym):
3664     "Emit symbol for short-range scan."
3665     srscan_window.move(w.i+1, w.j*2+2)
3666     srscan_window.addch(sym)
3667     srscan_window.refresh()
3668
3669 def boom(w):
3670     "Enemy fall down, go boom."
3671     if game.options & OPTION_CURSES:
3672         drawmaps(0)
3673         setwnd(srscan_window)
3674         srscan_window.attron(curses.A_REVERSE)
3675         put_srscan_sym(w, game.quad[w.i][w.j])
3676         #sound(500)
3677         #time.sleep(1.0)
3678         #nosound()
3679         srscan_window.attroff(curses.A_REVERSE)
3680         put_srscan_sym(w, game.quad[w.i][w.j])
3681         curses.delay_output(500)
3682         setwnd(message_window)
3683
3684 def warble():
3685     "Sound and visual effects for teleportation."
3686     if game.options & OPTION_CURSES:
3687         drawmaps(2)
3688         setwnd(message_window)
3689         #sound(50)
3690     prouts("     . . . . .     ")
3691     if game.options & OPTION_CURSES:
3692         #curses.delay_output(1000)
3693         #nosound()
3694         pass
3695
3696 def tracktorpedo(w, step, i, n, iquad):
3697     "Torpedo-track animation."
3698     if not game.options & OPTION_CURSES:
3699         if step == 1:
3700             if n != 1:
3701                 skip(1)
3702                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3703             else:
3704                 skip(1)
3705                 proutn(_("Torpedo track- "))
3706         elif step in {4, 9}:
3707             skip(1)
3708         proutn("%s   " % w)
3709     else:
3710         if not damaged(DSRSENS) or game.condition=="docked":
3711             if i != 0 and step == 1:
3712                 drawmaps(2)
3713                 time.sleep(0.4)
3714             if iquad in {'.', ' '}:
3715                 put_srscan_sym(w, '+')
3716                 #sound(step*10)
3717                 #time.sleep(0.1)
3718                 #nosound()
3719                 put_srscan_sym(w, iquad)
3720             else:
3721                 curwnd.attron(curses.A_REVERSE)
3722                 put_srscan_sym(w, iquad)
3723                 #sound(500)
3724                 #time.sleep(1.0)
3725                 #nosound()
3726                 curwnd.attroff(curses.A_REVERSE)
3727                 put_srscan_sym(w, iquad)
3728         else:
3729             proutn("%s   " % w)
3730
3731 def makechart():
3732     "Display the current galaxy chart."
3733     if game.options & OPTION_CURSES:
3734         setwnd(message_window)
3735         message_window.clear()
3736     chart()
3737     if game.options & OPTION_TTY:
3738         skip(1)
3739
3740 NSYM        = 14
3741
3742 def prstat(txt, data):
3743     proutn(txt)
3744     if game.options & OPTION_CURSES:
3745         skip(1)
3746         setwnd(status_window)
3747     else:
3748         proutn(" " * (NSYM - len(txt)))
3749     proutn(data)
3750     skip(1)
3751     if game.options & OPTION_CURSES:
3752         setwnd(report_window)
3753
3754 # Code from moving.c begins here
3755
3756 def imove(icourse=None, noattack=False):
3757     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3758     w = Coord()
3759
3760     def newquadrant(noattack):
3761         # Leaving quadrant -- allow final enemy attack
3762         # Don't set up attack if being pushed by nova or cloaked
3763         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3764             newcnd()
3765             for enemy in game.enemies:
3766                 finald = (w - enemy.location).distance()
3767                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3768             # Stas Sergeev added the condition
3769             # that attacks only happen if Klingons
3770             # are present and your skill is good.
3771             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3772                 attack(torps_ok=False)
3773             if game.alldone:
3774                 return
3775         # check for edge of galaxy
3776         kinks = 0
3777         while True:
3778             kink = False
3779             if icourse.final.i < 0:
3780                 icourse.final.i = -icourse.final.i
3781                 kink = True
3782             if icourse.final.j < 0:
3783                 icourse.final.j = -icourse.final.j
3784                 kink = True
3785             if icourse.final.i >= GALSIZE*QUADSIZE:
3786                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3787                 kink = True
3788             if icourse.final.j >= GALSIZE*QUADSIZE:
3789                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3790                 kink = True
3791             if kink:
3792                 kinks += 1
3793             else:
3794                 break
3795         if kinks:
3796             game.nkinks += 1
3797             if game.nkinks == 3:
3798                 # Three strikes -- you're out!
3799                 finish(FNEG3)
3800                 return
3801             skip(1)
3802             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3803             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3804             prout(_("YOU WILL BE DESTROYED."))
3805         # Compute final position in new quadrant
3806         if trbeam: # Don't bother if we are to be beamed
3807             return
3808         game.quadrant = icourse.final.quadrant()
3809         game.sector = icourse.final.sector()
3810         skip(1)
3811         prout(_("Entering Quadrant %s.") % game.quadrant)
3812         game.quad[game.sector.i][game.sector.j] = game.ship
3813         newqad()
3814         if game.skill>SKILL_NOVICE:
3815             attack(torps_ok=False)
3816
3817     def check_collision(h):
3818         iquad = game.quad[h.i][h.j]
3819         if iquad != '.':
3820             # object encountered in flight path
3821             stopegy = 50.0*icourse.distance/game.optime
3822             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3823                 for enemy in game.enemies:
3824                     if enemy.location == game.sector:
3825                         collision(rammed=False, enemy=enemy)
3826                         return True
3827                 # This should not happen
3828                 prout(_("Which way did he go?"))
3829                 return False
3830             elif iquad == ' ':
3831                 skip(1)
3832                 prouts(_("***RED ALERT!  RED ALERT!"))
3833                 skip(1)
3834                 proutn("***" + crmshp())
3835                 proutn(_(" pulled into black hole at Sector %s") % h)
3836                 # Getting pulled into a black hole was certain
3837                 # death in Almy's original.  Stas Sergeev added a
3838                 # possibility that you'll get timewarped instead.
3839                 n=0
3840                 for m in range(NDEVICES):
3841                     if game.damage[m]>0:
3842                         n += 1
3843                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3844                 if (game.options & OPTION_BLKHOLE) and rnd.withprob(1-probf):
3845                     timwrp()
3846                 else:
3847                     finish(FHOLE)
3848                 return True
3849             else:
3850                 # something else
3851                 skip(1)
3852                 proutn(crmshp())
3853                 if iquad == '#':
3854                     prout(_(" encounters Tholian web at %s;") % h)
3855                 else:
3856                     prout(_(" blocked by object at %s;") % h)
3857                 proutn(_("Emergency stop required "))
3858                 prout(_("%2d units of energy.") % int(stopegy))
3859                 game.energy -= stopegy
3860                 if game.energy <= 0:
3861                     finish(FNRG)
3862                 return True
3863         return False
3864
3865     trbeam = False
3866     if game.inorbit:
3867         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3868         game.inorbit = False
3869     # If tractor beam is to occur, don't move full distance
3870     if game.state.date+game.optime >= scheduled(FTBEAM):
3871         if game.iscloaked:
3872             # We can't be tractor beamed if cloaked,
3873             # so move the event into the future
3874             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
3875         else:
3876             trbeam = True
3877             game.condition = "red"
3878             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3879             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3880     # Move out
3881     game.quad[game.sector.i][game.sector.j] = '.'
3882     for _m in range(icourse.moves):
3883         icourse.nexttok()
3884         w = icourse.sector()
3885         if icourse.origin.quadrant() != icourse.location.quadrant():
3886             newquadrant(noattack)
3887             break
3888         elif check_collision(w):
3889             prout(_("Collision detected"))
3890             break
3891         else:
3892             game.sector = w
3893     # We're in destination quadrant -- compute new average enemy distances
3894     game.quad[game.sector.i][game.sector.j] = game.ship
3895     if game.enemies:
3896         for enemy in game.enemies:
3897             finald = (w-enemy.location).distance()
3898             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3899             enemy.kdist = finald
3900         sortenemies()
3901         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3902             attack(torps_ok=False)
3903         for enemy in game.enemies:
3904             enemy.kavgd = enemy.kdist
3905     newcnd()
3906     drawmaps(0)
3907     setwnd(message_window)
3908
3909 def dock(verbose):
3910     "Dock our ship at a starbase."
3911     scanner.chew()
3912     if game.condition == "docked" and verbose:
3913         prout(_("Already docked."))
3914         return
3915     if game.inorbit:
3916         prout(_("You must first leave standard orbit."))
3917         return
3918     if game.base is None or 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         curses.delay_output(500)
4573     if m > 3:
4574         game.quad[game.sector.i][game.sector.j]='?'
4575         game.alive = False
4576         drawmaps(1)
4577         setwnd(message_window)
4578         finish(FMATERIALIZE)
4579         return
4580     game.quad[game.sector.i][game.sector.j]=game.ship
4581     textcolor(GREEN)
4582     prout(_("succeeds."))
4583     textcolor(DEFAULT)
4584     dock(False)
4585     skip(1)
4586     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4587
4588 def abandon():
4589     "Abandon ship."
4590     scanner.chew()
4591     if game.condition=="docked":
4592         if game.ship!='E':
4593             prout(_("You cannot abandon Ye Faerie Queene."))
4594             return
4595     else:
4596         # Must take shuttle craft to exit
4597         if game.damage[DSHUTTL]==-1:
4598             prout(_("Ye Faerie Queene has no shuttle craft."))
4599             return
4600         if game.damage[DSHUTTL]<0:
4601             prout(_("Shuttle craft now serving Big Macs."))
4602             return
4603         if game.damage[DSHUTTL]>0:
4604             prout(_("Shuttle craft damaged."))
4605             return
4606         if game.landed:
4607             prout(_("You must be aboard the ship."))
4608             return
4609         if game.iscraft != "onship":
4610             prout(_("Shuttle craft not currently available."))
4611             return
4612         # Emit abandon ship messages
4613         skip(1)
4614         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4615         skip(1)
4616         prouts(_("***ALL HANDS ABANDON SHIP!"))
4617         skip(2)
4618         prout(_("Captain and crew escape in shuttle craft."))
4619         if not game.state.baseq:
4620             # Oops! no place to go...
4621             finish(FABANDN)
4622             return
4623         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4624         # Dispose of crew
4625         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4626             prout(_("Remainder of ship's complement beam down"))
4627             prout(_("to nearest habitable planet."))
4628         elif q.planet is not None and not damaged(DTRANSP):
4629             prout(_("Remainder of ship's complement beam down to %s.") %
4630                   q.planet)
4631         else:
4632             prout(_("Entire crew of %d left to die in outer space.") %
4633                   game.state.crew)
4634             game.casual += game.state.crew
4635             game.abandoned += game.state.crew
4636         # If at least one base left, give 'em the Faerie Queene
4637         skip(1)
4638         game.icrystl = False # crystals are lost
4639         game.nprobes = 0 # No probes
4640         prout(_("You are captured by Klingons and released to"))
4641         prout(_("the Federation in a prisoner-of-war exchange."))
4642         nb = rnd.integer(len(game.state.baseq))
4643         # Set up quadrant and position FQ adjacient to base
4644         if not game.quadrant == game.state.baseq[nb]:
4645             game.quadrant = game.state.baseq[nb]
4646             game.sector.i = game.sector.j = 5
4647             newqad()
4648         while True:
4649             # position next to base by trial and error
4650             game.quad[game.sector.i][game.sector.j] = '.'
4651             l = QUADSIZE
4652             for l in range(QUADSIZE):
4653                 game.sector = game.base.scatter()
4654                 if game.sector.valid_sector() and \
4655                        game.quad[game.sector.i][game.sector.j] == '.':
4656                     break
4657             if l < QUADSIZE:
4658                 break # found a spot
4659             game.sector.i=QUADSIZE/2
4660             game.sector.j=QUADSIZE/2
4661             newqad()
4662     # Get new commission
4663     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4664     game.state.crew = FULLCREW
4665     prout(_("Starfleet puts you in command of another ship,"))
4666     prout(_("the Faerie Queene, which is antiquated but,"))
4667     prout(_("still useable."))
4668     if game.icrystl:
4669         prout(_("The dilithium crystals have been moved."))
4670     game.imine = False
4671     game.iscraft = "offship" # Galileo disappears
4672     # Resupply ship
4673     game.condition="docked"
4674     for l in range(NDEVICES):
4675         game.damage[l] = 0.0
4676     game.damage[DSHUTTL] = -1
4677     game.energy = game.inenrg = 3000.0
4678     game.shield = game.inshld = 1250.0
4679     game.torps = game.intorps = 6
4680     game.lsupres=game.inlsr=3.0
4681     game.shldup=False
4682     game.warpfac=5.0
4683     game.brigfree = game.brigcapacity = 300
4684     return
4685
4686 # Code from planets.c begins here.
4687
4688 def consumeTime():
4689     "Abort a lengthy operation if an event interrupts it."
4690     game.ididit = True
4691     events()
4692     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4693         return True
4694     return False
4695
4696 def survey():
4697     "Report on (uninhabited) planets in the galaxy."
4698     iknow = False
4699     skip(1)
4700     scanner.chew()
4701     prout(_("Spock-  \"Planet report follows, Captain.\""))
4702     skip(1)
4703     for i in range(game.inplan):
4704         if game.state.planets[i].pclass == "destroyed":
4705             continue
4706         if (game.state.planets[i].known != "unknown" \
4707             and not game.state.planets[i].inhabited) \
4708             or game.idebug:
4709             iknow = True
4710             if game.idebug and game.state.planets[i].known=="unknown":
4711                 proutn("(Unknown) ")
4712             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4713             proutn(_("   class "))
4714             proutn(game.state.planets[i].pclass)
4715             proutn("   ")
4716             if game.state.planets[i].crystals != "present":
4717                 proutn(_("no "))
4718             prout(_("dilithium crystals present."))
4719             if game.state.planets[i].known=="shuttle_down":
4720                 prout(_("    Shuttle Craft Galileo on surface."))
4721     if not iknow:
4722         prout(_("No information available."))
4723
4724 def orbit():
4725     "Enter standard orbit."
4726     skip(1)
4727     scanner.chew()
4728     if game.inorbit:
4729         prout(_("Already in standard orbit."))
4730         return
4731     if damaged(DWARPEN) and damaged(DIMPULS):
4732         prout(_("Both warp and impulse engines damaged."))
4733         return
4734     if game.plnet is None:
4735         prout("There is no planet in this sector.")
4736         return
4737     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4738         prout(crmshp() + _(" not adjacent to planet."))
4739         skip(1)
4740         return
4741     game.optime = rnd.real(0.02, 0.05)
4742     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4743     newcnd()
4744     if consumeTime():
4745         return
4746     game.height = rnd.real(1400, 8600)
4747     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4748     game.inorbit = True
4749     game.ididit = True
4750
4751 def sensor():
4752     "Examine planets in this quadrant."
4753     if damaged(DSRSENS):
4754         if game.options & OPTION_TTY:
4755             prout(_("Short range sensors damaged."))
4756         return
4757     if game.iplnet is None:
4758         if game.options & OPTION_TTY:
4759             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4760         return
4761     if game.iplnet.known == "unknown":
4762         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4763         skip(1)
4764         prout(_("         Planet at Sector %s is of class %s.") %
4765               (game.plnet, game.iplnet.pclass))
4766         if game.iplnet.known=="shuttle_down":
4767             prout(_("         Sensors show Galileo still on surface."))
4768         proutn(_("         Readings indicate"))
4769         if game.iplnet.crystals != "present":
4770             proutn(_(" no"))
4771         prout(_(" dilithium crystals present.\""))
4772         if game.iplnet.known == "unknown":
4773             game.iplnet.known = "known"
4774     elif game.iplnet.inhabited:
4775         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4776         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4777
4778 def beam():
4779     "Use the transporter."
4780     nrgneed = 0
4781     scanner.chew()
4782     skip(1)
4783     if damaged(DTRANSP):
4784         prout(_("Transporter damaged."))
4785         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4786             skip(1)
4787             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4788             if ja():
4789                 shuttle()
4790         return
4791     if not game.inorbit:
4792         prout(crmshp() + _(" not in standard orbit."))
4793         return
4794     if game.shldup:
4795         prout(_("Impossible to transport through shields."))
4796         return
4797     if game.iplnet.known=="unknown":
4798         prout(_("Spock-  \"Captain, we have no information on this planet"))
4799         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4800         prout(_("  you may not go down.\""))
4801         return
4802     if not game.landed and game.iplnet.crystals=="absent":
4803         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4804         prout(_("  exploring a planet with no dilithium crystals."))
4805         proutn(_("  Are you sure this is wise?\" "))
4806         if not ja():
4807             scanner.chew()
4808             return
4809     if not (game.options & OPTION_PLAIN):
4810         nrgneed = 50 * game.skill + game.height / 100.0
4811         if nrgneed > game.energy:
4812             prout(_("Engineering to bridge--"))
4813             prout(_("  Captain, we don't have enough energy for transportation."))
4814             return
4815         if not game.landed and nrgneed * 2 > game.energy:
4816             prout(_("Engineering to bridge--"))
4817             prout(_("  Captain, we have enough energy only to transport you down to"))
4818             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4819             if game.iplnet.known == "shuttle_down":
4820                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4821             proutn(_("  Are you sure this is wise?\" "))
4822             if not ja():
4823                 scanner.chew()
4824                 return
4825     if game.landed:
4826         # Coming from planet
4827         if game.iplnet.known=="shuttle_down":
4828             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4829             if ja():
4830                 scanner.chew()
4831                 return
4832             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4833         prout(_("Landing party assembled, ready to beam up."))
4834         skip(1)
4835         prout(_("Kirk whips out communicator..."))
4836         prouts(_("BEEP  BEEP  BEEP"))
4837         skip(2)
4838         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4839     else:
4840         # Going to planet
4841         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4842         skip(1)
4843         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4844         skip(1)
4845         prout(_("Kirk-  \"Energize.\""))
4846     game.ididit = True
4847     skip(1)
4848     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4849     skip(2)
4850     if not rnd.withprob(0.98):
4851         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4852         skip(2)
4853         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4854         finish(FLOST)
4855         return
4856     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4857     game.landed = not game.landed
4858     game.energy -= nrgneed
4859     skip(2)
4860     prout(_("Transport complete."))
4861     if game.landed and game.iplnet.known=="shuttle_down":
4862         prout(_("The shuttle craft Galileo is here!"))
4863     if not game.landed and game.imine:
4864         game.icrystl = True
4865         game.cryprob = 0.05
4866     game.imine = False
4867     return
4868
4869 def mine():
4870     "Strip-mine a world for dilithium."
4871     skip(1)
4872     scanner.chew()
4873     if not game.landed:
4874         prout(_("Mining party not on planet."))
4875         return
4876     if game.iplnet.crystals == "mined":
4877         prout(_("This planet has already been strip-mined for dilithium."))
4878         return
4879     elif game.iplnet.crystals == "absent":
4880         prout(_("No dilithium crystals on this planet."))
4881         return
4882     if game.imine:
4883         prout(_("You've already mined enough crystals for this trip."))
4884         return
4885     if game.icrystl and game.cryprob == 0.05:
4886         prout(_("With all those fresh crystals aboard the ") + crmshp())
4887         prout(_("there's no reason to mine more at this time."))
4888         return
4889     game.optime = rnd.real(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4890     if consumeTime():
4891         return
4892     prout(_("Mining operation complete."))
4893     game.iplnet.crystals = "mined"
4894     game.imine = game.ididit = True
4895
4896 def usecrystals():
4897     "Use dilithium crystals."
4898     game.ididit = False
4899     skip(1)
4900     scanner.chew()
4901     if not game.icrystl:
4902         prout(_("No dilithium crystals available."))
4903         return
4904     if game.energy >= 1000:
4905         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4906         prout(_("  except when Condition Yellow exists."))
4907         return
4908     prout(_("Spock- \"Captain, I must warn you that loading"))
4909     prout(_("  raw dilithium crystals into the ship's power"))
4910     prout(_("  system may risk a severe explosion."))
4911     proutn(_("  Are you sure this is wise?\" "))
4912     if not ja():
4913         scanner.chew()
4914         return
4915     skip(1)
4916     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4917     prout(_("  Mr. Spock and I will try it.\""))
4918     skip(1)
4919     prout(_("Spock-  \"Crystals in place, Sir."))
4920     prout(_("  Ready to activate circuit.\""))
4921     skip(1)
4922     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4923     skip(1)
4924     if rnd.withprob(game.cryprob):
4925         prouts(_("  \"Activating now! - - No good!  It's***"))
4926         skip(2)
4927         prouts(_("***RED ALERT!  RED A*L********************************"))
4928         skip(1)
4929         stars()
4930         prouts(_("******************   KA-BOOM!!!!   *******************"))
4931         skip(1)
4932         kaboom()
4933         return
4934     game.energy += rnd.real(5000.0, 5500.0)
4935     prouts(_("  \"Activating now! - - "))
4936     prout(_("The instruments"))
4937     prout(_("   are going crazy, but I think it's"))
4938     prout(_("   going to work!!  Congratulations, Sir!\""))
4939     game.cryprob *= 2.0
4940     game.ididit = True
4941
4942 def shuttle():
4943     "Use shuttlecraft for planetary jaunt."
4944     scanner.chew()
4945     skip(1)
4946     if damaged(DSHUTTL):
4947         if game.damage[DSHUTTL] == -1.0:
4948             if game.inorbit and game.iplnet.known == "shuttle_down":
4949                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4950             else:
4951                 prout(_("Ye Faerie Queene had no shuttle craft."))
4952         elif game.damage[DSHUTTL] > 0:
4953             prout(_("The Galileo is damaged."))
4954         else: # game.damage[DSHUTTL] < 0
4955             prout(_("Shuttle craft is now serving Big Macs."))
4956         return
4957     if not game.inorbit:
4958         prout(crmshp() + _(" not in standard orbit."))
4959         return
4960     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4961         prout(_("Shuttle craft not currently available."))
4962         return
4963     if not game.landed and game.iplnet.known=="shuttle_down":
4964         prout(_("You will have to beam down to retrieve the shuttle craft."))
4965         return
4966     if game.shldup or game.condition == "docked":
4967         prout(_("Shuttle craft cannot pass through shields."))
4968         return
4969     if game.iplnet.known=="unknown":
4970         prout(_("Spock-  \"Captain, we have no information on this planet"))
4971         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4972         prout(_("  you may not fly down.\""))
4973         return
4974     game.optime = 3.0e-5*game.height
4975     if game.optime >= 0.8*game.state.remtime:
4976         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4977         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4978                int(100*game.optime/game.state.remtime))
4979         prout(_("remaining time."))
4980         proutn(_("Are you sure this is wise?\" "))
4981         if not ja():
4982             game.optime = 0.0
4983             return
4984     if game.landed:
4985         # Kirk on planet
4986         if game.iscraft == "onship":
4987             # Galileo on ship!
4988             if not damaged(DTRANSP):
4989                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4990                 if ja():
4991                     beam()
4992                     return
4993                 proutn(_("Shuttle crew"))
4994             else:
4995                 proutn(_("Rescue party"))
4996             prout(_(" boards Galileo and swoops toward planet surface."))
4997             game.iscraft = "offship"
4998             skip(1)
4999             if consumeTime():
5000                 return
5001             game.iplnet.known="shuttle_down"
5002             prout(_("Trip complete."))
5003             return
5004         else:
5005             # Ready to go back to ship
5006             prout(_("You and your mining party board the"))
5007             prout(_("shuttle craft for the trip back to the Enterprise."))
5008             skip(1)
5009             prouts(_("The short hop begins . . ."))
5010             skip(1)
5011             game.iplnet.known="known"
5012             game.icraft = True
5013             skip(1)
5014             game.landed = False
5015             if consumeTime():
5016                 return
5017             game.iscraft = "onship"
5018             game.icraft = False
5019             if game.imine:
5020                 game.icrystl = True
5021                 game.cryprob = 0.05
5022             game.imine = False
5023             prout(_("Trip complete."))
5024             return
5025     else:
5026         # Kirk on ship and so is Galileo
5027         prout(_("Mining party assembles in the hangar deck,"))
5028         prout(_("ready to board the shuttle craft \"Galileo\"."))
5029         skip(1)
5030         prouts(_("The hangar doors open; the trip begins."))
5031         skip(1)
5032         game.icraft = True
5033         game.iscraft = "offship"
5034         if consumeTime():
5035             return
5036         game.iplnet.known = "shuttle_down"
5037         game.landed = True
5038         game.icraft = False
5039         prout(_("Trip complete."))
5040         return
5041
5042 def deathray():
5043     "Use the big zapper."
5044     game.ididit = False
5045     skip(1)
5046     scanner.chew()
5047     if game.ship != 'E':
5048         prout(_("Ye Faerie Queene has no death ray."))
5049         return
5050     if len(game.enemies)==0:
5051         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
5052         return
5053     if damaged(DDRAY):
5054         prout(_("Death Ray is damaged."))
5055         return
5056     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
5057     prout(_("  is highly unpredictible.  Considering the alternatives,"))
5058     proutn(_("  are you sure this is wise?\" "))
5059     if not ja():
5060         return
5061     prout(_("Spock-  \"Acknowledged.\""))
5062     skip(1)
5063     game.ididit = True
5064     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
5065     skip(1)
5066     prout(_("Crew scrambles in emergency preparation."))
5067     prout(_("Spock and Scotty ready the death ray and"))
5068     prout(_("prepare to channel all ship's power to the device."))
5069     skip(1)
5070     prout(_("Spock-  \"Preparations complete, sir.\""))
5071     prout(_("Kirk-  \"Engage!\""))
5072     skip(1)
5073     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5074     skip(1)
5075     dprob = 0.30
5076     if game.options & OPTION_PLAIN:
5077         dprob = 0.5
5078     r = rnd.real()
5079     if r > dprob:
5080         prouts(_("Sulu- \"Captain!  It's working!\""))
5081         skip(2)
5082         while len(game.enemies) > 0:
5083             deadkl(game.enemies[-1].location, game.quad[game.enemies[-1].location.i][game.enemies[-1].location.j],game.enemies[-1].location)
5084         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5085         if game.unwon() == 0:
5086             finish(FWON)
5087         if (game.options & OPTION_PLAIN) == 0:
5088             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5089             if rnd.withprob(0.05):
5090                 prout(_("   is still operational.\""))
5091             else:
5092                 prout(_("   has been rendered nonfunctional.\""))
5093                 game.damage[DDRAY] = 39.95
5094         return
5095     r = rnd.real()        # Pick failure method
5096     if r <= 0.30:
5097         prouts(_("Sulu- \"Captain!  It's working!\""))
5098         skip(1)
5099         prouts(_("***RED ALERT!  RED ALERT!"))
5100         skip(1)
5101         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5102         skip(1)
5103         prouts(_("***RED ALERT!  RED A*L********************************"))
5104         skip(1)
5105         stars()
5106         prouts(_("******************   KA-BOOM!!!!   *******************"))
5107         skip(1)
5108         kaboom()
5109         return
5110     if r <= 0.55:
5111         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5112         skip(1)
5113         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5114         skip(1)
5115         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5116         prout(_("  have apparently been transformed into strange mutations."))
5117         prout(_("  Vulcans do not seem to be affected."))
5118         skip(1)
5119         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5120         finish(FDRAY)
5121         return
5122     if r <= 0.75:
5123         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5124         skip(2)
5125         proutn(_("Spock-  \"I believe the word is"))
5126         prouts(_(" *ASTONISHING*"))
5127         prout(_(" Mr. Sulu."))
5128         for i in range(QUADSIZE):
5129             for j in range(QUADSIZE):
5130                 if game.quad[i][j] == '.':
5131                     game.quad[i][j] = '?'
5132         prout(_("  Captain, our quadrant is now infested with"))
5133         prouts(_(" - - - - - -  *THINGS*."))
5134         skip(1)
5135         prout(_("  I have no logical explanation.\""))
5136         return
5137     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5138     skip(1)
5139     prout(_("Scotty-  \"There are so many tribbles down here"))
5140     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5141     finish(FTRIBBLE)
5142     return
5143
5144 # Code from reports.c begins here
5145
5146 def attackreport(curt):
5147     "eport status of bases under attack."
5148     if not curt:
5149         if is_scheduled(FCDBAS):
5150             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5151             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5152         elif game.isatb == 1:
5153             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5154             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5155         else:
5156             prout(_("No Starbase is currently under attack."))
5157     else:
5158         if is_scheduled(FCDBAS):
5159             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5160         if game.isatb:
5161             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5162         clreol()
5163
5164 def report():
5165     # report on general game status
5166     scanner.chew()
5167     # pylint: disable=consider-using-ternary
5168     s1 = (game.thawed and _("thawed ")) or ""
5169     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5170     s3 = (None, _("novice"), _("fair"),
5171           _("good"), _("expert"), _("emeritus"))[game.skill]
5172     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5173     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5174         prout(_("No plaque is allowed."))
5175     if game.tourn:
5176         prout(_("This is tournament game %d.") % game.tourn)
5177     prout(_("Your secret password is \"%s\"") % game.passwd)
5178     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5179                                                       (game.inkling + game.incom + game.inscom)))
5180     if game.incom - len(game.state.kcmdr):
5181         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5182     elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
5183         prout(_(", but no Commanders."))
5184     else:
5185         prout(".")
5186     if game.skill > SKILL_FAIR:
5187         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5188     if len(game.state.baseq) != game.inbase:
5189         proutn(_("There "))
5190         if game.inbase-len(game.state.baseq)==1:
5191             proutn(_("has been 1 base"))
5192         else:
5193             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5194         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5195     else:
5196         prout(_("There are %d bases.") % game.inbase)
5197     if communicating() or game.iseenit:
5198         # Don't report this if not seen and
5199         # either the radio is dead or not at base!
5200         attackreport(False)
5201         game.iseenit = True
5202     if game.casual:
5203         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5204     if game.brigcapacity != game.brigfree:
5205         embriggened = game.brigcapacity-game.brigfree
5206         if embriggened == 1:
5207             prout(_("1 Klingon in brig"))
5208         else:
5209             prout(_("%d Klingons in brig.") %  embriggened)
5210         if game.kcaptured == 0:
5211             pass
5212         elif game.kcaptured == 1:
5213             prout(_("1 captured Klingon turned in to Starfleet."))
5214         else:
5215             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5216     if game.nhelp:
5217         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5218     if game.ship == 'E':
5219         proutn(_("You have "))
5220         if game.nprobes:
5221             proutn("%d" % (game.nprobes))
5222         else:
5223             proutn(_("no"))
5224         proutn(_(" deep space probe"))
5225         if game.nprobes!=1:
5226             proutn(_("s"))
5227         prout(".")
5228     if communicating() and is_scheduled(FDSPROB):
5229         if game.isarmed:
5230             proutn(_("An armed deep space probe is in "))
5231         else:
5232             proutn(_("A deep space probe is in "))
5233         prout("Quadrant %s." % game.probe.quadrant())
5234     if game.icrystl:
5235         if game.cryprob <= .05:
5236             prout(_("Dilithium crystals aboard ship... not yet used."))
5237         else:
5238             i=0
5239             ai = 0.05
5240             while game.cryprob > ai:
5241                 ai *= 2.0
5242                 i += 1
5243             prout(_("Dilithium crystals have been used %d time%s.") % \
5244                   (i, (_("s"), "")[i==1]))
5245     skip(1)
5246
5247 def lrscan(silent):
5248     "Long-range sensor scan."
5249     if damaged(DLRSENS):
5250         # Now allow base's sensors if docked
5251         if game.condition != "docked":
5252             if not silent:
5253                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5254             return
5255         if not silent:
5256             prout(_("Starbase's long-range scan"))
5257     elif not silent:
5258         prout(_("Long-range scan"))
5259     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5260         if not silent:
5261             proutn(" ")
5262         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5263             if not Coord(x, y).valid_quadrant():
5264                 if not silent:
5265                     proutn("  -1")
5266             else:
5267                 if not damaged(DRADIO):
5268                     game.state.galaxy[x][y].charted = True
5269                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5270                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5271                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5272                 if not silent and game.state.galaxy[x][y].supernova:
5273                     proutn(" ***")
5274                 elif not silent:
5275                     cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
5276                     proutn(((3 - len(cn)) * '.') + cn)
5277         if not silent:
5278             prout(" ")
5279
5280 def damagereport():
5281     "Damage report."
5282     jdam = False
5283     scanner.chew()
5284     for i in range(NDEVICES):
5285         if damaged(i):
5286             if not jdam:
5287                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5288                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5289                 jdam = True
5290             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5291                                                game.damage[i]+0.05,
5292                                                DOCKFAC*game.damage[i]+0.005))
5293     if not jdam:
5294         prout(_("All devices functional."))
5295
5296 def rechart():
5297     "Update the chart in the Enterprise's computer from galaxy data."
5298     game.lastchart = game.state.date
5299     for i in range(GALSIZE):
5300         for j in range(GALSIZE):
5301             if game.state.galaxy[i][j].charted:
5302                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5303                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5304                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5305
5306 def chart():
5307     "Display the star chart."
5308     scanner.chew()
5309     if (game.options & OPTION_AUTOSCAN):
5310         lrscan(silent=True)
5311     if communicating():
5312         rechart()
5313     if game.lastchart < game.state.date and game.condition == "docked":
5314         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5315         rechart()
5316     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5317     if game.state.date > game.lastchart:
5318         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5319     prout("      1    2    3    4    5    6    7    8")
5320     for i in range(GALSIZE):
5321         proutn("%d |" % (i+1))
5322         for j in range(GALSIZE):
5323             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5324                 proutn("<")
5325             else:
5326                 proutn(" ")
5327             if game.state.galaxy[i][j].supernova:
5328                 show = "***"
5329             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5330                 show = ".1."
5331             elif game.state.galaxy[i][j].charted:
5332                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5333                 if (game.options & OPTION_DOTFILL):
5334                     show = show.replace(" ", ".")
5335             else:
5336                 show = "..."
5337             proutn(show)
5338             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5339                 proutn(">")
5340             else:
5341                 proutn(" ")
5342         proutn("  |")
5343         if i<GALSIZE:
5344             skip(1)
5345
5346 def sectscan(goodScan, i, j):
5347     "Light up an individual dot in a sector."
5348     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5349         if game.quad[i][j] in ('E', 'F'):
5350             if game.iscloaked:
5351                 highvideo()
5352             textcolor({"green":GREEN,
5353                        "yellow":YELLOW,
5354                        "red":RED,
5355                        "docked":CYAN,
5356                        "dead":BROWN}[game.condition])
5357         else:
5358             textcolor({'?':LIGHTMAGENTA,
5359                        'K':LIGHTRED,
5360                        'S':LIGHTRED,
5361                        'C':LIGHTRED,
5362                        'R':LIGHTRED,
5363                        'T':LIGHTRED,
5364                        '@':LIGHTGREEN,
5365                        'P':LIGHTGREEN,
5366                       }.get(game.quad[i][j], DEFAULT))
5367         proutn("%c " % game.quad[i][j])
5368         textcolor(DEFAULT)
5369     else:
5370         proutn("- ")
5371
5372 def status(req=0):
5373     "Emit status report lines"
5374     if not req or req == 1:
5375         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5376                % (game.state.date, game.state.remtime))
5377     if not req or req == 2:
5378         if game.condition != "docked":
5379             newcnd()
5380         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5381                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5382         if game.iscloaked:
5383             prout(_(", CLOAKED"))
5384     if not req or req == 3:
5385         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5386     if not req or req == 4:
5387         if damaged(DLIFSUP):
5388             if game.condition == "docked":
5389                 s = _("DAMAGED, Base provides")
5390             else:
5391                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5392         else:
5393             s = _("ACTIVE")
5394         prstat(_("Life Support"), s)
5395     if not req or req == 5:
5396         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5397     if not req or req == 6:
5398         extra = ""
5399         if game.icrystl and (game.options & OPTION_SHOWME):
5400             extra = _(" (have crystals)")
5401         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5402     if not req or req == 7:
5403         prstat(_("Torpedoes"), "%d" % (game.torps))
5404     if not req or req == 8:
5405         if damaged(DSHIELD):
5406             s = _("DAMAGED,")
5407         elif game.shldup:
5408             s = _("UP,")
5409         else:
5410             s = _("DOWN,")
5411         data = _(" %d%% %.1f units") \
5412                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5413         prstat(_("Shields"), s+data)
5414     if not req or req == 9:
5415         prstat(_("Klingons Left"), "%d" % game.unwon())
5416     if not req or req == 10:
5417         if game.options & OPTION_WORLDS:
5418             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5419             if plnet and plnet.inhabited:
5420                 prstat(_("Major system"), plnet.name)
5421             else:
5422                 prout(_("Sector is uninhabited"))
5423     elif not req or req == 11:
5424         attackreport(not req)
5425
5426 def request():
5427     "Request specified status data, a historical relic from slow TTYs."
5428     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5429     while scanner.nexttok() == "IHEOL":
5430         proutn(_("Information desired? "))
5431     scanner.chew()
5432     if scanner.token in requests:
5433         status(requests.index(scanner.token))
5434     else:
5435         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5436         prout(("  date, condition, position, lsupport, warpfactor,"))
5437         prout(("  energy, torpedoes, shields, klingons, system, time."))
5438
5439 def srscan():
5440     "Short-range scan."
5441     goodScan=True
5442     if damaged(DSRSENS):
5443         # Allow base's sensors if docked
5444         if game.condition != "docked":
5445             prout(_("   S.R. SENSORS DAMAGED!"))
5446             goodScan=False
5447         else:
5448             prout(_("  [Using Base's sensors]"))
5449     else:
5450         prout(_("     Short-range scan"))
5451     if goodScan and communicating():
5452         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5453         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5454         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5455         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5456     prout("    1 2 3 4 5 6 7 8 9 10")
5457     if game.condition != "docked":
5458         newcnd()
5459     for i in range(QUADSIZE):
5460         proutn("%2d  " % (i+1))
5461         for j in range(QUADSIZE):
5462             sectscan(goodScan, i, j)
5463         skip(1)
5464
5465 def eta():
5466     "Use computer to get estimated time of arrival for a warp jump."
5467     w1 = Coord(); w2 = Coord()
5468     prompt = False
5469     if damaged(DCOMPTR):
5470         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5471         skip(1)
5472         return
5473     if scanner.nexttok() != "IHREAL":
5474         prompt = True
5475         scanner.chew()
5476         proutn(_("Destination quadrant and/or sector? "))
5477         if scanner.nexttok()!="IHREAL":
5478             huh()
5479             return
5480     w1.j = int(scanner.real-0.5)
5481     if scanner.nexttok() != "IHREAL":
5482         huh()
5483         return
5484     w1.i = int(scanner.real-0.5)
5485     if scanner.nexttok() == "IHREAL":
5486         w2.j = int(scanner.real-0.5)
5487         if scanner.nexttok() != "IHREAL":
5488             huh()
5489             return
5490         w2.i = int(scanner.real-0.5)
5491     else:
5492         if game.quadrant.j>w1.i:
5493             w2.i = 0
5494         else:
5495             w2.i=QUADSIZE-1
5496         if game.quadrant.i>w1.j:
5497             w2.j = 0
5498         else:
5499             w2.j=QUADSIZE-1
5500     if not w1.valid_quadrant() or not w2.valid_sector():
5501         huh()
5502         return
5503     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5504                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5505     wfl = False
5506     if prompt:
5507         prout(_("Answer \"no\" if you don't know the value:"))
5508     while True:
5509         scanner.chew()
5510         proutn(_("Time or arrival date? "))
5511         if scanner.nexttok()=="IHREAL":
5512             ttime = scanner.real
5513             if ttime > game.state.date:
5514                 ttime -= game.state.date # Actually a star date
5515             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5516             if ttime <= 1e-10 or twarp > 10:
5517                 prout(_("We'll never make it, sir."))
5518                 scanner.chew()
5519                 return
5520             if twarp < 1.0:
5521                 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     if klumper > MAXKLQUAD:
5801         klumper = MAXKLQUAD
5802     while True:
5803         r = rnd.real()
5804         klump = int((1.0 - r*r)*klumper)
5805         if klump > krem:
5806             klump = krem
5807         krem -= klump
5808         while True:
5809             w = randplace(GALSIZE)
5810             if not game.state.galaxy[w.i][w.j].supernova and \
5811                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5812                 break
5813         game.state.galaxy[w.i][w.j].klingons += klump
5814         if krem <= 0:
5815             break
5816     # Position Klingon Commander Ships
5817     for i in range(game.incom):
5818         while True:
5819             w = randplace(GALSIZE)
5820             if not welcoming(w) or w in game.state.kcmdr:
5821                 continue
5822             if (game.state.galaxy[w.i][w.j].klingons or rnd.withprob(0.25)):
5823                 break
5824         game.state.galaxy[w.i][w.j].klingons += 1
5825         game.state.kcmdr.append(w)
5826     # Locate planets in galaxy
5827     for i in range(game.inplan):
5828         while True:
5829             w = randplace(GALSIZE)
5830             if game.state.galaxy[w.i][w.j].planet is None:
5831                 break
5832         new = Planet()
5833         new.quadrant = w
5834         new.crystals = "absent"
5835         if (game.options & OPTION_WORLDS) and i < NINHAB:
5836             new.pclass = "M"        # All inhabited planets are class M
5837             new.crystals = "absent"
5838             new.known = "known"
5839             new.name = systnames[i]
5840             new.inhabited = True
5841         else:
5842             new.pclass = ("M", "N", "O")[rnd.integer(0, 3)]
5843             if rnd.withprob(0.33):
5844                 new.crystals = "present"
5845             new.known = "unknown"
5846             new.inhabited = False
5847         game.state.galaxy[w.i][w.j].planet = new
5848         game.state.planets.append(new)
5849     # Locate Romulans
5850     for i in range(game.state.nromrem):
5851         w = randplace(GALSIZE)
5852         game.state.galaxy[w.i][w.j].romulans += 1
5853     # Place the Super-Commander if needed
5854     if game.state.nscrem > 0:
5855         while True:
5856             w = randplace(GALSIZE)
5857             if welcoming(w):
5858                 break
5859         game.state.kscmdr = w
5860         game.state.galaxy[w.i][w.j].klingons += 1
5861     # Initialize times for extraneous events
5862     schedule(FSNOVA, expran(0.5 * game.intime))
5863     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5864     schedule(FSNAP, rnd.real(1.0, 2.0)) # Force an early snapshot
5865     schedule(FBATTAK, expran(0.3*game.intime))
5866     unschedule(FCDBAS)
5867     if game.state.nscrem:
5868         schedule(FSCMOVE, 0.2777)
5869     else:
5870         unschedule(FSCMOVE)
5871     unschedule(FSCDBAS)
5872     unschedule(FDSPROB)
5873     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5874         schedule(FDISTR, expran(1.0 + game.intime))
5875     else:
5876         unschedule(FDISTR)
5877     unschedule(FENSLV)
5878     unschedule(FREPRO)
5879     # Place thing (in tournament game, we don't want one!)
5880     # New in SST2K: never place the Thing near a starbase.
5881     # This makes sense and avoids a special case in the old code.
5882     global thing
5883     if game.tourn is None:
5884         while True:
5885             thing = randplace(GALSIZE)
5886             if thing not in game.state.baseq:
5887                 break
5888     skip(2)
5889     game.state.snap = False
5890     if game.skill == SKILL_NOVICE:
5891         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5892         prout(_("a deadly Klingon invasion force. As captain of the United"))
5893         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5894         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5895         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5896         prout(_("your mission.  As you proceed you may be given more time."))
5897         skip(1)
5898         prout(_("You will have %d supporting starbases.") % (game.inbase))
5899         proutn(_("Starbase locations-  "))
5900     else:
5901         prout(_("Stardate %d.") % int(game.state.date))
5902         skip(1)
5903         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5904         prout(_("An unknown number of Romulans."))
5905         if game.state.nscrem:
5906             prout(_("And one (GULP) Super-Commander."))
5907         prout(_("%d stardates.") % int(game.intime))
5908         proutn(_("%d starbases in ") % game.inbase)
5909     for i in range(game.inbase):
5910         proutn(repr(game.state.baseq[i]))
5911         proutn("  ")
5912     skip(2)
5913     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5914     proutn(_(" Sector %s") % game.sector)
5915     skip(2)
5916     prout(_("Good Luck!"))
5917     if game.state.nscrem:
5918         prout(_("  YOU'LL NEED IT."))
5919     waitfor()
5920     clrscr()
5921     setwnd(message_window)
5922     newqad()
5923     if len(game.enemies) - (thing == game.quadrant) - (game.tholian is not None):
5924         game.shldup = True
5925     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5926         attack(torps_ok=False)
5927
5928 def choose():
5929     "Choose your game type."
5930     while True:
5931         game.tourn = game.length = 0
5932         game.thawed = False
5933         game.skill = SKILL_NONE
5934         # Do not chew here, we want to use command-line tokens
5935         if not scanner.inqueue: # Can start with command line options
5936             proutn(_("Would you like a regular, tournament, or saved game? "))
5937         scanner.nexttok()
5938         if scanner.sees("tournament"):
5939             while scanner.nexttok() == "IHEOL":
5940                 proutn(_("Type in tournament number-"))
5941             if scanner.real == 0:
5942                 scanner.chew()
5943                 continue # We don't want a blank entry
5944             game.tourn = int(round(scanner.real))
5945             rnd.seed(scanner.real)
5946             if logfp:
5947                 logfp.write("# rnd.seed(%d)\n" % scanner.real)
5948             break
5949         if scanner.sees("saved") or scanner.sees("frozen"):
5950             if thaw():
5951                 continue
5952             scanner.chew()
5953             if game.passwd is None:
5954                 continue
5955             if not game.alldone:
5956                 game.thawed = True # No plaque if not finished
5957             report()
5958             waitfor()
5959             return True
5960         if scanner.sees("regular"):
5961             break
5962         proutn(_("What is \"%s\"? ") % scanner.token)
5963         scanner.chew()
5964     while game.length==0 or game.skill==SKILL_NONE:
5965         if scanner.nexttok() == "IHALPHA":
5966             if scanner.sees("short"):
5967                 game.length = 1
5968             elif scanner.sees("medium"):
5969                 game.length = 2
5970             elif scanner.sees("long"):
5971                 game.length = 4
5972             elif scanner.sees("novice"):
5973                 game.skill = SKILL_NOVICE
5974             elif scanner.sees("fair"):
5975                 game.skill = SKILL_FAIR
5976             elif scanner.sees("good"):
5977                 game.skill = SKILL_GOOD
5978             elif scanner.sees("expert"):
5979                 game.skill = SKILL_EXPERT
5980             elif scanner.sees("emeritus"):
5981                 game.skill = SKILL_EMERITUS
5982             else:
5983                 proutn(_("What is \""))
5984                 proutn(scanner.token)
5985                 prout("\"?")
5986         else:
5987             scanner.chew()
5988             if game.length==0:
5989                 proutn(_("Would you like a Short, Medium, or Long game? "))
5990             elif game.skill == SKILL_NONE:
5991                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5992     # Choose game options -- added by ESR for SST2K
5993     if scanner.nexttok() != "IHALPHA":
5994         scanner.chew()
5995         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5996         scanner.nexttok()
5997     if scanner.sees("plain"):
5998         # Approximates the UT FORTRAN version.
5999         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)
6000         game.options |= OPTION_PLAIN
6001     elif scanner.sees("almy"):
6002         # Approximates Tom Almy's version.
6003         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_DOTFILL)
6004         game.options |= OPTION_ALMY
6005     elif scanner.sees("fancy") or scanner.sees("\n"):
6006         pass
6007     elif len(scanner.token):
6008         proutn(_("What is \"%s\"?") % scanner.token)
6009     setpassword()
6010     if game.passwd == "debug":
6011         game.idebug = True
6012         prout("=== Debug mode enabled.")
6013     # Use parameters to generate initial values of things
6014     game.damfac = 0.5 * game.skill
6015     game.inbase = rnd.integer(BASEMIN, BASEMAX+1)
6016     game.inplan = 0
6017     if game.options & OPTION_PLANETS:
6018         game.inplan += rnd.integer(MAXUNINHAB/2, MAXUNINHAB+1)
6019     if game.options & OPTION_WORLDS:
6020         game.inplan += int(NINHAB)
6021     game.state.nromrem = game.inrom = rnd.integer(2 * game.skill)
6022     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
6023     game.state.remtime = 7.0 * game.length
6024     game.intime = game.state.remtime
6025     game.inkling = int(2.0*game.intime*((game.skill+1 - 2*rnd.real())*game.skill*0.1+.15))
6026     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*rnd.real()))
6027     game.state.remres = (game.inkling+4*game.incom)*game.intime
6028     game.inresor = game.state.remres
6029     if game.inkling > 50:
6030         game.inbase += 1
6031     return False
6032
6033 def dropin(iquad=None):
6034     "Drop a feature on a random dot in the current quadrant."
6035     while True:
6036         w = randplace(QUADSIZE)
6037         if game.quad[w.i][w.j] == '.':
6038             break
6039     if iquad is not None:
6040         game.quad[w.i][w.j] = iquad
6041     return w
6042
6043 def newcnd():
6044     "Update our alert status."
6045     game.condition = "green"
6046     if game.energy < 1000.0:
6047         game.condition = "yellow"
6048     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
6049         game.condition = "red"
6050     if not game.alive:
6051         game.condition="dead"
6052
6053 def newkling():
6054     "Drop new Klingon into current quadrant."
6055     return Enemy('K', loc=dropin(), power=rnd.real(300,450)+25.0*game.skill)
6056
6057 def sortenemies():
6058     "Sort enemies by distance so 'nearest' is meaningful."
6059     game.enemies.sort(key=lambda x: x.kdist)
6060
6061 def newqad():
6062     "Set up a new state of quadrant, for when we enter or re-enter it."
6063     game.justin = True
6064     game.iplnet = None
6065     game.neutz = game.inorbit = game.landed = False
6066     game.ientesc = game.iseenit = game.isviolreported = False
6067     game.tholian = None
6068     # Create a blank quadrant
6069     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6070     if game.iscate:
6071         # Attempt to escape Super-commander, so tbeam back!
6072         game.iscate = False
6073         game.ientesc = True
6074     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6075     # cope with supernova
6076     if q.supernova:
6077         return
6078     game.klhere = q.klingons
6079     game.irhere = q.romulans
6080     # Position Starship
6081     game.quad[game.sector.i][game.sector.j] = game.ship
6082     game.enemies = []
6083     if q.klingons:
6084         # Position ordinary Klingons
6085         for _i in range(game.klhere):
6086             newkling()
6087         # If we need a commander, promote a Klingon
6088         for cmdr in game.state.kcmdr:
6089             if cmdr == game.quadrant:
6090                 e = game.enemies[game.klhere-1]
6091                 game.quad[e.location.i][e.location.j] = 'C'
6092                 e.power = rnd.real(950,1350) + 50.0*game.skill
6093                 break
6094         # If we need a super-commander, promote a Klingon
6095         if game.quadrant == game.state.kscmdr:
6096             e = game.enemies[0]
6097             game.quad[e.location.i][e.location.j] = 'S'
6098             e.power = rnd.real(1175.0,  1575.0) + 125.0*game.skill
6099             game.iscate = (game.remkl() > 1)
6100     # Put in Romulans if needed
6101     for _i in range(q.romulans):
6102         Enemy('R', loc=dropin(), power=rnd.real(400.0,850.0)+50.0*game.skill)
6103     # If quadrant needs a starbase, put it in
6104     if q.starbase:
6105         game.base = dropin('B')
6106     # If quadrant needs a planet, put it in
6107     if q.planet:
6108         game.iplnet = q.planet
6109         if not q.planet.inhabited:
6110             game.plnet = dropin('P')
6111         else:
6112             game.plnet = dropin('@')
6113     # Check for condition
6114     newcnd()
6115     # Check for RNZ
6116     if game.irhere > 0 and game.klhere == 0:
6117         game.neutz = True
6118         if not damaged(DRADIO):
6119             skip(1)
6120             prout(_("LT. Uhura- \"Captain, an urgent message."))
6121             prout(_("  I'll put it on audio.\"  CLICK"))
6122             skip(1)
6123             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6124             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6125     # Put in THING if needed
6126     if thing == game.quadrant:
6127         Enemy(etype='?', loc=dropin(),
6128               power=rnd.real(6000,6500.0)+250.0*game.skill)
6129         if not damaged(DSRSENS):
6130             skip(1)
6131             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6132             prout(_("    Please examine your short-range scan.\""))
6133     # Decide if quadrant needs a Tholian; lighten up if skill is low
6134     if game.options & OPTION_THOLIAN:
6135         if (game.skill < SKILL_GOOD and rnd.withprob(0.02)) or \
6136             (game.skill == SKILL_GOOD and rnd.withprob(0.05)) or \
6137             (game.skill > SKILL_GOOD and rnd.withprob(0.08)):
6138             w = Coord()
6139             while True:
6140                 w.i = rnd.withprob(0.5) * (QUADSIZE-1)
6141                 w.j = rnd.withprob(0.5) * (QUADSIZE-1)
6142                 if game.quad[w.i][w.j] == '.':
6143                     break
6144             game.tholian = Enemy(etype='T', loc=w,
6145                                  power=rnd.integer(100, 500) + 25.0*game.skill)
6146             # Reserve unoccupied corners
6147             if game.quad[0][0]=='.':
6148                 game.quad[0][0] = 'X'
6149             if game.quad[0][QUADSIZE-1]=='.':
6150                 game.quad[0][QUADSIZE-1] = 'X'
6151             if game.quad[QUADSIZE-1][0]=='.':
6152                 game.quad[QUADSIZE-1][0] = 'X'
6153             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6154                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6155     sortenemies()
6156     # And finally the stars
6157     for _i in range(q.stars):
6158         dropin('*')
6159     # Put in a few black holes
6160     for _i in range(1, 3+1):
6161         if rnd.withprob(0.5):
6162             dropin(' ')
6163     # Take out X's in corners if Tholian present
6164     if game.tholian:
6165         if game.quad[0][0]=='X':
6166             game.quad[0][0] = '.'
6167         if game.quad[0][QUADSIZE-1]=='X':
6168             game.quad[0][QUADSIZE-1] = '.'
6169         if game.quad[QUADSIZE-1][0]=='X':
6170             game.quad[QUADSIZE-1][0] = '.'
6171         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6172             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6173     # This should guarantee that replay games don't lose info about the chart
6174     if (game.options & OPTION_AUTOSCAN) or replayfp:
6175         lrscan(silent=True)
6176
6177 def setpassword():
6178     "Set the self-destruct password."
6179     if game.options & OPTION_PLAIN:
6180         while True:
6181             scanner.chew()
6182             proutn(_("Please type in a secret password- "))
6183             scanner.nexttok()
6184             game.passwd = scanner.token
6185             if game.passwd is not None:
6186                 break
6187     else:
6188         game.passwd = ""
6189         game.passwd += chr(ord('a')+rnd.integer(26))
6190         game.passwd += chr(ord('a')+rnd.integer(26))
6191         game.passwd += chr(ord('a')+rnd.integer(26))
6192
6193 # Code from sst.c begins here
6194
6195 commands = [
6196     ("SRSCAN",           OPTION_TTY),
6197     ("STATUS",           OPTION_TTY),
6198     ("REQUEST",          OPTION_TTY),
6199     ("LRSCAN",           OPTION_TTY),
6200     ("PHASERS",          0),
6201     ("TORPEDO",          0),
6202     ("PHOTONS",          0),
6203     ("MOVE",             0),
6204     ("SHIELDS",          0),
6205     ("DOCK",             0),
6206     ("DAMAGES",          0),
6207     ("CHART",            0),
6208     ("IMPULSE",          0),
6209     ("REST",             0),
6210     ("WARP",             0),
6211     ("SENSORS",          OPTION_PLANETS),
6212     ("ORBIT",            OPTION_PLANETS),
6213     ("TRANSPORT",        OPTION_PLANETS),
6214     ("MINE",             OPTION_PLANETS),
6215     ("CRYSTALS",         OPTION_PLANETS),
6216     ("SHUTTLE",          OPTION_PLANETS),
6217     ("PLANETS",          OPTION_PLANETS),
6218     ("REPORT",           0),
6219     ("COMPUTER",         0),
6220     ("COMMANDS",         0),
6221     ("EMEXIT",           0),
6222     ("PROBE",            OPTION_PROBE),
6223     ("SAVE",             0),
6224     ("FREEZE",           0),        # Synonym for SAVE
6225     ("ABANDON",          0),
6226     ("DESTRUCT",         0),
6227     ("DEATHRAY",         0),
6228     ("CAPTURE",          OPTION_CAPTURE),
6229     ("CLOAK",            OPTION_CLOAK),
6230     ("DEBUG",            0),
6231     ("MAYDAY",           0),
6232     ("SOS",              0),        # Synonym for MAYDAY
6233     ("CALL",             0),        # Synonym for MAYDAY
6234     ("QUIT",             0),
6235     ("HELP",             0),
6236     ("SCORE",            0),
6237     ("CURSES",            0),
6238     ("",                 0),
6239 ]
6240
6241 def listCommands():
6242     "Generate a list of legal commands."
6243     prout(_("LEGAL COMMANDS ARE:"))
6244     emitted = 0
6245     for (key, opt) in commands:
6246         if not opt or (opt & game.options):
6247             proutn("%-12s " % key)
6248             emitted += 1
6249             if emitted % 5 == 4:
6250                 skip(1)
6251     skip(1)
6252
6253 def helpme():
6254     "Browse on-line help."
6255     key = scanner.nexttok()
6256     while True:
6257         if key == "IHEOL":
6258             setwnd(prompt_window)
6259             proutn(_("Help on what command? "))
6260             key = scanner.nexttok()
6261         setwnd(message_window)
6262         if key == "IHEOL":
6263             return
6264         cmds = [x[0] for x in commands]
6265         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6266             break
6267         skip(1)
6268         listCommands()
6269         key = "IHEOL"
6270         scanner.chew()
6271         skip(1)
6272     cmd = scanner.token.upper()
6273     for directory in docpath:
6274         try:
6275             fp = open(os.path.join(directory, "sst.doc"), "r")
6276             break
6277         except IOError:
6278             pass
6279     else:
6280         prout(_("Spock-  \"Captain, that information is missing from the"))
6281         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6282         proutn(_("   in these directories: %s") % ":".join(docpath))
6283         prout(".\"")
6284         # This used to continue: "You need to find SST.DOC and put
6285         # it in the current directory."
6286         return
6287     while True:
6288         linebuf = fp.readline()
6289         if linebuf == '':
6290             prout(_("Spock- \"Captain, there is no information on that command.\""))
6291             fp.close()
6292             return
6293         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6294             linebuf = linebuf[3:].strip()
6295             if cmd.upper() == linebuf:
6296                 break
6297     skip(1)
6298     prout(_("Spock- \"Captain, I've found the following information:\""))
6299     skip(1)
6300     while True:
6301         linebuf = fp.readline()
6302         if "******" in linebuf:
6303             break
6304         proutn(linebuf)
6305     fp.close()
6306
6307 def makemoves():
6308     "Command-interpretation loop."
6309     def checkviol():
6310         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6311             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6312             game.ncviol += 1
6313             game.isviolreported = True
6314     while True:         # command loop
6315         drawmaps(1)
6316         while True:        # get a command
6317             hitme = False
6318             game.optime = game.justin = False
6319             scanner.chew()
6320             setwnd(prompt_window)
6321             clrscr()
6322             proutn("COMMAND> ")
6323             if scanner.nexttok() == "IHEOL":
6324                 if game.options & OPTION_CURSES:
6325                     makechart()
6326                 continue
6327             elif scanner.token == "":
6328                 continue
6329             game.ididit = False
6330             clrscr()
6331             setwnd(message_window)
6332             clrscr()
6333             abandon_passed = False
6334             cmd = ""    # Force cmd to persist after loop
6335             opt = 0     # Force opt to persist after loop
6336             for (cmd, opt) in commands:
6337                 # commands after ABANDON cannot be abbreviated
6338                 if cmd == "ABANDON":
6339                     abandon_passed = True
6340                 if cmd == scanner.token.upper() or (not abandon_passed \
6341                         and cmd.startswith(scanner.token.upper())):
6342                     break
6343             if cmd == "":
6344                 listCommands()
6345                 continue
6346             elif opt and not (opt & game.options):
6347                 huh()
6348             else:
6349                 break
6350         if game.options & OPTION_CURSES:
6351             prout("COMMAND> %s" % cmd)
6352         if cmd == "SRSCAN":                # srscan
6353             srscan()
6354         elif cmd == "STATUS":                # status
6355             status()
6356         elif cmd == "REQUEST":                # status request
6357             request()
6358         elif cmd == "LRSCAN":                # long range scan
6359             lrscan(silent=False)
6360         elif cmd == "PHASERS":                # phasers
6361             phasers()
6362             if game.ididit:
6363                 checkviol()
6364                 hitme = True
6365         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6366             torps()
6367             if game.ididit:
6368                 checkviol()
6369                 hitme = True
6370         elif cmd == "MOVE":                # move under warp
6371             warp(wcourse=None, involuntary=False)
6372         elif cmd == "SHIELDS":                # shields
6373             doshield(shraise=False)
6374             if game.ididit:
6375                 hitme = True
6376                 game.shldchg = False
6377         elif cmd == "DOCK":                # dock at starbase
6378             dock(True)
6379             if game.ididit:
6380                 attack(torps_ok=False)
6381         elif cmd == "DAMAGES":                # damage reports
6382             damagereport()
6383         elif cmd == "CHART":                # chart
6384             makechart()
6385         elif cmd == "IMPULSE":                # impulse
6386             impulse()
6387         elif cmd == "REST":                # rest
6388             wait()
6389             if game.ididit:
6390                 hitme = True
6391         elif cmd == "WARP":                # warp
6392             setwarp()
6393         elif cmd == "SENSORS":                # sensors
6394             sensor()
6395         elif cmd == "ORBIT":                # orbit
6396             orbit()
6397             if game.ididit:
6398                 hitme = True
6399         elif cmd == "TRANSPORT":                # transport "beam"
6400             beam()
6401         elif cmd == "MINE":                # mine
6402             mine()
6403             if game.ididit:
6404                 hitme = True
6405         elif cmd == "CRYSTALS":                # crystals
6406             usecrystals()
6407             if game.ididit:
6408                 hitme = True
6409         elif cmd == "SHUTTLE":                # shuttle
6410             shuttle()
6411             if game.ididit:
6412                 hitme = True
6413         elif cmd == "PLANETS":                # Planet list
6414             survey()
6415         elif cmd == "REPORT":                # Game Report
6416             report()
6417         elif cmd == "COMPUTER":                # use COMPUTER!
6418             eta()
6419         elif cmd == "COMMANDS":
6420             listCommands()
6421         elif cmd == "EMEXIT":                # Emergency exit
6422             clrscr()                        # Hide screen
6423             freeze(True)                # forced save
6424             raise SystemExit(1)                # And quick exit
6425         elif cmd == "PROBE":
6426             probe()                        # Launch probe
6427             if game.ididit:
6428                 hitme = True
6429         elif cmd == "ABANDON":                # Abandon Ship
6430             abandon()
6431         elif cmd == "DESTRUCT":                # Self Destruct
6432             selfdestruct()
6433         elif cmd == "SAVE":                # Save Game
6434             freeze(False)
6435             clrscr()
6436             if game.skill > SKILL_GOOD:
6437                 prout(_("WARNING--Saved games produce no plaques!"))
6438         elif cmd == "DEATHRAY":                # Try a desparation measure
6439             deathray()
6440             if game.ididit:
6441                 hitme = True
6442         elif cmd == "CAPTURE":
6443             capture()
6444         elif cmd == "CLOAK":
6445             cloak()
6446         elif cmd == "DEBUGCMD":                # What do we want for debug???
6447             debugme()
6448         elif cmd == "MAYDAY":                # Call for help
6449             mayday()
6450             if game.ididit:
6451                 hitme = True
6452         elif cmd == "QUIT":
6453             game.alldone = True                # quit the game
6454         elif cmd == "HELP":
6455             helpme()                        # get help
6456         elif cmd == "SCORE":
6457             score()                         # see current score
6458         elif cmd == "CURSES":
6459             game.options |= (OPTION_CURSES | OPTION_COLOR)
6460             iostart()
6461         while True:
6462             if game.alldone:
6463                 break                # Game has ended
6464             if game.optime != 0.0:
6465                 events()
6466                 if game.alldone:
6467                     break        # Events did us in
6468             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6469                 atover(False)
6470                 continue
6471             if hitme and not game.justin:
6472                 attack(torps_ok=True)
6473                 if game.alldone:
6474                     break
6475                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6476                     atover(False)
6477                     hitme = True
6478                     continue
6479             break
6480         if game.alldone:
6481             break
6482     if game.idebug:
6483         prout("=== Ending")
6484
6485 def cramen(ch):
6486     "Emit the name of an enemy or feature."
6487     if   ch == 'R': s = _("Romulan")
6488     elif ch == 'K': s = _("Klingon")
6489     elif ch == 'C': s = _("Commander")
6490     elif ch == 'S': s = _("Super-commander")
6491     elif ch == '*': s = _("Star")
6492     elif ch == 'P': s = _("Planet")
6493     elif ch == 'B': s = _("Starbase")
6494     elif ch == ' ': s = _("Black hole")
6495     elif ch == 'T': s = _("Tholian")
6496     elif ch == '#': s = _("Tholian web")
6497     elif ch == '?': s = _("Stranger")
6498     elif ch == '@': s = _("Inhabited World")
6499     else: s = "Unknown??"
6500     return s
6501
6502 def crmena(loud, enemy, loctype, w):
6503     "Emit the name of an enemy and his location."
6504     buf = ""
6505     if loud:
6506         buf += "***"
6507     buf += cramen(enemy) + _(" at ")
6508     if loctype == "quadrant":
6509         buf += _("Quadrant ")
6510     elif loctype == "sector":
6511         buf += _("Sector ")
6512     return buf + repr(w)
6513
6514 def crmshp():
6515     "Emit our ship name."
6516     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6517
6518 def stars():
6519     "Emit a line of stars"
6520     prouts("******************************************************")
6521     skip(1)
6522
6523 def expran(avrage):
6524     return -avrage*math.log(1e-7 + rnd.real())
6525
6526 def randplace(size):
6527     "Choose a random location."
6528     w = Coord()
6529     w.i = rnd.integer(size)
6530     w.j = rnd.integer(size)
6531     return w
6532
6533 class sstscanner:
6534     def __init__(self):
6535         self.type = None
6536         self.token = None
6537         self.real = 0.0
6538         self.inqueue = []
6539     def nexttok(self):
6540         # Get a token from the user
6541         self.real = 0.0
6542         self.token = ''
6543         # Fill the token quue if nothing here
6544         while not self.inqueue:
6545             sline = cgetline()
6546             if curwnd==prompt_window:
6547                 clrscr()
6548                 setwnd(message_window)
6549                 clrscr()
6550             if sline == '':
6551                 return None
6552             if not sline:
6553                 continue
6554             else:
6555                 self.inqueue = sline.lstrip().split() + ["\n"]
6556         # From here on in it's all looking at the queue
6557         self.token = self.inqueue.pop(0)
6558         if self.token == "\n":
6559             self.type = "IHEOL"
6560             return "IHEOL"
6561         try:
6562             self.real = float(self.token)
6563             self.type = "IHREAL"
6564             return "IHREAL"
6565         except ValueError:
6566             pass
6567         # Treat as alpha
6568         self.token = self.token.lower()
6569         self.type = "IHALPHA"
6570         self.real = None
6571         return "IHALPHA"
6572     def append(self, tok):
6573         self.inqueue.append(tok)
6574     def push(self, tok):
6575         self.inqueue.insert(0, tok)
6576     def waiting(self):
6577         return self.inqueue
6578     def chew(self):
6579         # Demand input for next scan
6580         self.inqueue = []
6581         self.real = self.token = None
6582     def sees(self, s):
6583         # compares s to item and returns true if it matches to the length of s
6584         return s.startswith(self.token)
6585     def int(self):
6586         # Round token value to nearest integer
6587         return int(round(self.real))
6588     def getcoord(self):
6589         s = Coord()
6590         self.nexttok()
6591         if self.type != "IHREAL":
6592             huh()
6593             return None
6594         s.i = self.int()-1
6595         self.nexttok()
6596         if self.type != "IHREAL":
6597             huh()
6598             return None
6599         s.j = self.int()-1
6600         return s
6601     def __repr__(self):
6602         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6603
6604 def ja():
6605     "Yes-or-no confirmation."
6606     scanner.chew()
6607     while True:
6608         scanner.nexttok()
6609         if scanner.token == 'y':
6610             return True
6611         if scanner.token == 'n':
6612             return False
6613         scanner.chew()
6614         proutn(_("Please answer with \"y\" or \"n\": "))
6615
6616 def huh():
6617     "Complain about unparseable input."
6618     scanner.chew()
6619     skip(1)
6620     prout(_("Beg your pardon, Captain?"))
6621
6622 def debugme():
6623     "Access to the internals for debugging."
6624     proutn("Reset levels? ")
6625     if ja():
6626         if game.energy < game.inenrg:
6627             game.energy = game.inenrg
6628         game.shield = game.inshld
6629         game.torps = game.intorps
6630         game.lsupres = game.inlsr
6631     proutn("Reset damage? ")
6632     if ja():
6633         for i in range(NDEVICES):
6634             if game.damage[i] > 0.0:
6635                 game.damage[i] = 0.0
6636     proutn("Toggle debug flag? ")
6637     if ja():
6638         game.idebug = not game.idebug
6639         if game.idebug:
6640             prout("Debug output ON")
6641         else:
6642             prout("Debug output OFF")
6643     proutn("Cause selective damage? ")
6644     if ja():
6645         for i in range(NDEVICES):
6646             proutn("Kill %s?" % device[i])
6647             scanner.chew()
6648             key = scanner.nexttok()
6649             if key == "IHALPHA" and scanner.sees("y"):
6650                 game.damage[i] = 10.0
6651     proutn("Examine/change events? ")
6652     if ja():
6653         ev = Event()
6654         w = Coord()
6655         legends = {
6656             FSNOVA:  "Supernova       ",
6657             FTBEAM:  "T Beam          ",
6658             FSNAP:   "Snapshot        ",
6659             FBATTAK: "Base Attack     ",
6660             FCDBAS:  "Base Destroy    ",
6661             FSCMOVE: "SC Move         ",
6662             FSCDBAS: "SC Base Destroy ",
6663             FDSPROB: "Probe Move      ",
6664             FDISTR:  "Distress Call   ",
6665             FENSLV:  "Enslavement     ",
6666             FREPRO:  "Klingon Build   ",
6667         }
6668         for i in range(1, NEVENTS):
6669             proutn(legends[i])
6670             if is_scheduled(i):
6671                 proutn("%.2f" % (scheduled(i)-game.state.date))
6672                 if i in {FENSLV, FREPRO}:
6673                     ev = findevent(i)
6674                     proutn(" in %s" % ev.quadrant)
6675             else:
6676                 proutn("never")
6677             proutn("? ")
6678             scanner.chew()
6679             key = scanner.nexttok()
6680             if key == 'n':
6681                 unschedule(i)
6682                 scanner.chew()
6683             elif key == "IHREAL":
6684                 ev = schedule(i, scanner.real)
6685                 if i in {FENSLV, FREPRO}:
6686                     scanner.chew()
6687                     proutn("In quadrant- ")
6688                     key = scanner.nexttok()
6689                     # "IHEOL" says to leave coordinates as they are
6690                     if key != "IHEOL":
6691                         if key != "IHREAL":
6692                             prout("Event %d canceled, no x coordinate." % (i))
6693                             unschedule(i)
6694                             continue
6695                         w.i = int(round(scanner.real))
6696                         key = scanner.nexttok()
6697                         if key != "IHREAL":
6698                             prout("Event %d canceled, no y coordinate." % (i))
6699                             unschedule(i)
6700                             continue
6701                         w.j = int(round(scanner.real))
6702                         ev.quadrant = w
6703         scanner.chew()
6704     proutn("Induce supernova here? ")
6705     if ja():
6706         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6707         atover(True)
6708
6709 if __name__ == '__main__':
6710     try:
6711         #global line, thing, game
6712         game = None
6713         thing = Thingy()
6714         game = Gamestate()
6715         rnd = randomizer()
6716         logfp = None
6717         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6718         if os.getenv("TERM"):
6719             game.options |= OPTION_CURSES
6720         else:
6721             game.options |= OPTION_TTY
6722         seed = int(time.time())
6723         (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
6724         replay = False
6725         for (switch, val) in options:
6726             if switch == '-r':
6727                 # pylint: disable=raise-missing-from
6728                 try:
6729                     replayfp = open(val, "r")
6730                 except IOError:
6731                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6732                     raise SystemExit(1)
6733                 # pylint: disable=raise-missing-from
6734                 try:
6735                     line = replayfp.readline().strip()
6736                     (leader, __, seed) = line.split()
6737                     # pylint: disable=eval-used
6738                     seed = eval(seed)
6739                     line = replayfp.readline().strip()
6740                     arguments += line.split()[2:]
6741                     replay = True
6742                 except ValueError:
6743                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6744                     raise SystemExit(1)
6745                 game.options |= OPTION_TTY
6746                 game.options &=~ OPTION_CURSES
6747             elif switch == '-s':
6748                 seed = int(val)
6749             elif switch == '-t':
6750                 game.options |= OPTION_TTY
6751                 game.options &=~ OPTION_CURSES
6752             elif switch == '-x':
6753                 game.idebug = True
6754             elif switch == '-c':        # Enable curses debugging - undocumented
6755                 game.cdebug = True
6756             elif switch == '-V':
6757                 print("SST2K", version)
6758                 raise SystemExit(0)
6759             else:
6760                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6761                 raise SystemExit(1)
6762         # where to save the input in case of bugs
6763         if "TMPDIR" in os.environ:
6764             tmpdir = os.environ['TMPDIR']
6765         else:
6766             tmpdir = "/tmp"
6767         try:
6768             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6769         except IOError:
6770             sys.stderr.write("sst: warning, can't open logfile\n")
6771             sys.exit(1)
6772         if logfp:
6773             logfp.write("# seed %s\n" % seed)
6774             logfp.write("# options %s\n" % " ".join(arguments))
6775             logfp.write("# SST2K version %s\n" % version)
6776             logfp.write("# recorded by %s@%s on %s\n" % \
6777                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6778         rnd.seed(seed)
6779         scanner = sstscanner()
6780         for arg in arguments:
6781             scanner.append(arg)
6782         try:
6783             iostart()
6784             while True: # Play a game
6785                 setwnd(fullscreen_window)
6786                 clrscr()
6787                 prelim()
6788                 setup()
6789                 if game.alldone:
6790                     score()
6791                     game.alldone = False
6792                 else:
6793                     makemoves()
6794                 if replay:
6795                     break
6796                 skip(1)
6797                 stars()
6798                 skip(1)
6799                 if game.tourn and game.alldone:
6800                     proutn(_("Do you want your score recorded?"))
6801                     if ja():
6802                         scanner.chew()
6803                         scanner.push("\n")
6804                         freeze(False)
6805                 scanner.chew()
6806                 proutn(_("Do you want to play again? "))
6807                 if not ja():
6808                     break
6809             skip(1)
6810             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6811         finally:
6812             ioend()
6813         raise SystemExit(0)
6814     except KeyboardInterrupt:
6815         if logfp:
6816             logfp.close()
6817         print("")
6818
6819 # End.