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