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