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