Implement alphameric Y coordinates.
[super-star-trek.git] / sst
1 #!/usr/bin/env python3
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 # Copyright by Eric S. Raymond
15 # SPDX-License-Identifier: BSD-2-clause
16
17 # pylint: disable=line-too-long,superfluous-parens,too-many-lines,invalid-name,missing-function-docstring,missing-class-docstring,multiple-statements,too-many-branches,too-many-statements,too-many-locals,too-many-nested-blocks,too-many-return-statements,too-many-instance-attributes,global-statement,no-else-break,no-else-return,no-else-continue,too-few-public-methods,too-many-boolean-expressions,consider-using-f-string,consider-using-enumerate,consider-using-with,unspecified-encoding
18
19 # pylint: disable=multiple-imports
20 import os, sys, math, curses, time, pickle, copy, gettext, getpass
21 import getopt, socket, locale
22 import codecs
23
24 # This import only works on Unixes.  The intention is to enable
25 # Ctrl-P, Ctrl-N, and friends in Cmd.
26 try:
27     # pylint: disable=unused-import
28     import readline
29 except ImportError:
30     pass
31
32 version = "2.7"
33
34 docpath         = (".", "doc/", "/usr/share/doc/sst/")
35
36 def _(st):
37     return gettext.gettext(st)
38
39 # Rolling our own LCG because Python changed its incompatibly in 3.2.
40 # Thus, we needed to have our own to be 2/3 polyglot; it will be
41 # helpful when and if we ever forward-port to another language.
42
43 class randomizer:
44     # LCG PRNG parameters tested against
45     # Knuth vol. 2. by the authors of ADVENT
46     LCG_A = 1093
47     LCG_C = 221587
48     LCG_M = 1048576
49
50     @staticmethod
51     def random():
52         old_x = game.lcg_x
53         game.lcg_x = (randomizer.LCG_A * game.lcg_x + randomizer.LCG_C) % randomizer.LCG_M
54         return old_x / randomizer.LCG_M
55
56     @staticmethod
57     def withprob(p):
58         v = randomizer.random()
59         #if logfp:
60         #    logfp.write("#withprob(%.2f) -> %s\n" % (p, v < p))
61         return v < p
62
63     @staticmethod
64     def integer(*args):
65         v = randomizer.random()
66         if len(args) == 1:
67             v = int(v * args[0])
68         else:
69             v = args[0] + int(v * (args[1] - args[0]))
70         #if logfp:
71         #    logfp.write("#integer%s -> %s\n" % (args, v))
72         return int(v)
73
74     @staticmethod
75     def real(*args):
76         v = randomizer.random()
77         if len(args) == 1:
78             v *= args[0]                 # from [0, args[0])
79         elif len(args) == 2:
80             v = args[0] + v*(args[1]-args[0])        # from [args[0], args[1])
81         #if logfp:
82         #    logfp.write("#real%s -> %f\n" % (args, v))
83         return v
84
85     @staticmethod
86     def seed(n):
87         #if logfp:
88         #    logfp.write("#seed(%d)\n" % n)
89         game.lcg_x = n % randomizer.LCG_M
90
91 GALSIZE         = 8             # Galaxy size in quadrants
92 NINHAB          = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
93 MAXUNINHAB      = 10            # Maximum uninhabited worlds
94 QUADSIZE        = 10            # Quadrant size in sectors
95 BASEMIN         = 2                             # Minimum starbases
96 BASEMAX         = (GALSIZE * GALSIZE // 12)     # Maximum starbases
97 MAXKLGAME       = 127           # Maximum Klingons per game
98 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
99 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong
100 FOREVER         = 1e30          # Time for the indefinite future
101 MAXBURST        = 3             # Max # of torps you can launch in one turn
102 MINCMDR         = 10            # Minimum number of Klingon commanders
103 DOCKFAC         = 0.25          # Repair faster when docked
104 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
105
106 ALGERON         = 2311          # Date of the Treaty of Algeron
107
108
109 DEFAULT      = -1
110 BLACK        = 0
111 BLUE         = 1
112 GREEN        = 2
113 CYAN         = 3
114 RED          = 4
115 MAGENTA      = 5
116 BROWN        = 6
117 LIGHTGRAY    = 7
118 DARKGRAY     = 8
119 LIGHTBLUE    = 9
120 LIGHTGREEN   = 10
121 LIGHTCYAN    = 11
122 LIGHTRED     = 12
123 LIGHTMAGENTA = 13
124 YELLOW       = 14
125 WHITE        = 15
126
127 class TrekError(Exception):
128     pass
129
130 class JumpOut(Exception):
131     pass
132
133 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         prout(_("You are left with your landing party on"))
3183         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3184         skip(1)
3185         prout(_("They are very fond of \"Captain Kirk\" soup."))
3186         skip(1)
3187         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3188     elif ifin == FDPLANET:
3189         prout(_("You and your mining party perish."))
3190         skip(1)
3191         prout(_("That was a great shot."))
3192         skip(1)
3193     elif ifin == FSSC:
3194         prout(_("The Galileo is instantly annihilated by the supernova."))
3195         prout(_("You and your mining party are atomized."))
3196         skip(1)
3197         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3198         prout(_("joins the Romulans, wreaking terror on the Federation."))
3199     elif ifin == FPNOVA:
3200         prout(_("You and your mining party are atomized."))
3201         skip(1)
3202         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3203         prout(_("joins the Romulans, wreaking terror on the Federation."))
3204     elif ifin == FSTRACTOR:
3205         prout(_("The shuttle craft Galileo is also caught,"))
3206         prout(_("and breaks up under the strain."))
3207         skip(1)
3208         prout(_("Your debris is scattered for millions of miles."))
3209         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3210     elif ifin == FDRAY:
3211         prout(_("The mutants attack and kill Spock."))
3212         prout(_("Your ship is captured by Klingons, and"))
3213         prout(_("your crew is put on display in a Klingon zoo."))
3214     elif ifin == FTRIBBLE:
3215         prout(_("Tribbles consume all remaining water,"))
3216         prout(_("food, and oxygen on your ship."))
3217         skip(1)
3218         prout(_("You die of thirst, starvation, and asphyxiation."))
3219         prout(_("Your starship is a derelict in space."))
3220     elif ifin == FHOLE:
3221         prout(_("Your ship is drawn to the center of the black hole."))
3222         prout(_("You are crushed into extremely dense matter."))
3223     elif ifin == FCLOAK:
3224         game.ncviol += 1
3225         prout(_("You have violated the Treaty of Algeron."))
3226         prout(_("The Romulan Empire can never trust you again."))
3227     elif ifin == FCREW:
3228         prout(_("Your last crew member has died."))
3229     if ifin != FWON and ifin != FCLOAK and game.iscloaked:
3230         prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
3231         prout(_("You may have missed some warning messages."))
3232         skip(1)
3233     if game.ship == 'F':
3234         game.ship = None
3235     elif game.ship == 'E':
3236         game.ship = 'F'
3237     game.alive = False
3238     if game.unwon() != 0:
3239         goodies = game.state.remres/game.inresor
3240         baddies = (game.remkl() + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3241         if goodies/baddies >= rnd.real(1.0, 1.5):
3242             prout(_("As a result of your actions, a treaty with the Klingon"))
3243             prout(_("Empire has been signed. The terms of the treaty are"))
3244             if goodies/baddies >= rnd.real(3.0):
3245                 prout(_("favorable to the Federation."))
3246                 skip(1)
3247                 prout(_("Congratulations!"))
3248             else:
3249                 prout(_("highly unfavorable to the Federation."))
3250         else:
3251             prout(_("The Federation will be destroyed."))
3252     else:
3253         prout(_("Since you took the last Klingon with you, you are a"))
3254         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3255         prout(_("statue in your memory. Rest in peace, and try not"))
3256         prout(_("to think about pigeons."))
3257         game.gamewon = True
3258     score()
3259     scanner.chew()      # Clean up leftovers
3260
3261 def score():
3262     "Compute player's score."
3263     timused = game.state.date - game.indate
3264     if (timused == 0 or game.unwon() != 0) and timused < 5.0:
3265         timused = 5.0
3266     game.perdate = killrate()
3267     ithperd = 500*game.perdate + 0.5
3268     iwon = 0
3269     if game.gamewon:
3270         iwon = 100*game.skill
3271     if game.ship == 'E':
3272         klship = 0
3273     elif game.ship == 'F':
3274         klship = 1
3275     else:
3276         klship = 2
3277     dead_ordinaries= game.inkling - game.remkl() + len(game.state.kcmdr) + game.state.nscrem
3278     game.score = 10*(dead_ordinaries)\
3279              + 50*(game.incom - len(game.state.kcmdr)) \
3280              + ithperd + iwon \
3281              + 20*(game.inrom - game.state.nromrem) \
3282              + 200*(game.inscom - game.state.nscrem) \
3283                  - game.state.nromrem \
3284              + 3 * game.kcaptured \
3285              - badpoints()
3286     if not game.alive:
3287         game.score -= 200
3288     skip(2)
3289     prout(_("Your score --"))
3290     if game.inrom - game.state.nromrem:
3291         prout(_("%6d Romulans destroyed                 %5d") %
3292               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3293     if game.state.nromrem and game.gamewon:
3294         prout(_("%6d Romulans captured                  %5d") %
3295               (game.state.nromrem, game.state.nromrem))
3296     if dead_ordinaries:
3297         prout(_("%6d ordinary Klingons destroyed        %5d") %
3298               (dead_ordinaries, 10*dead_ordinaries))
3299     if game.incom - len(game.state.kcmdr):
3300         prout(_("%6d Klingon commanders destroyed       %5d") %
3301               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3302     if game.kcaptured:
3303         prout(_("%d Klingons captured                   %5d") %
3304               (game.kcaptured, 3 * game.kcaptured))
3305     if game.inscom - game.state.nscrem:
3306         prout(_("%6d Super-Commander destroyed          %5d") %
3307               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3308     if ithperd:
3309         prout(_("%6.2f Klingons per stardate              %5d") %
3310               (game.perdate, ithperd))
3311     if game.state.starkl:
3312         prout(_("%6d stars destroyed by your action     %5d") %
3313               (game.state.starkl, -5*game.state.starkl))
3314     if game.state.nplankl:
3315         prout(_("%6d planets destroyed by your action   %5d") %
3316               (game.state.nplankl, -10*game.state.nplankl))
3317     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3318         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3319               (game.state.nworldkl, -300*game.state.nworldkl))
3320     if game.state.basekl:
3321         prout(_("%6d bases destroyed by your action     %5d") %
3322               (game.state.basekl, -100*game.state.basekl))
3323     if game.nhelp:
3324         prout(_("%6d calls for help from starbase       %5d") %
3325               (game.nhelp, -45*game.nhelp))
3326     if game.casual:
3327         prout(_("%6d casualties incurred                %5d") %
3328               (game.casual, -game.casual))
3329     if game.abandoned:
3330         prout(_("%6d crew abandoned in space            %5d") %
3331               (game.abandoned, -3*game.abandoned))
3332     if klship:
3333         prout(_("%6d ship(s) lost or destroyed          %5d") %
3334               (klship, -100*klship))
3335     if game.ncviol > 0:
3336         if game.ncviol == 1:
3337             prout(_("1 Treaty of Algeron violation          -100"))
3338         else:
3339             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3340                   (game.ncviol, -100*game.ncviol))
3341     if not game.alive:
3342         prout(_("Penalty for getting yourself killed        -200"))
3343     if game.gamewon:
3344         proutn(_("Bonus for winning "))
3345         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3346         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3347         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3348         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3349         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3350         prout("           %5d" % iwon)
3351     skip(1)
3352     prout(_("TOTAL SCORE                               %5d") % game.score)
3353
3354 def plaque():
3355     "Emit winner's commemmorative plaque."
3356     skip(2)
3357     while True:
3358         proutn(_("File or device name for your plaque: "))
3359         winner = cgetline()
3360         try:
3361             fp = open(winner, "w")
3362             break
3363         except IOError:
3364             prout(_("Invalid name."))
3365
3366     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3367     winner = cgetline()
3368     # The 38 below must be 64 for 132-column paper
3369     nskip = 38 - len(winner)/2
3370     # This is where the ASCII art picture was emitted.
3371     # It got garbled somewhere in the chain of transmission to the Almy version.
3372     # We should restore it if we can find old enough FORTRAN sources.
3373     fp.write("\n\n\n")
3374     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3375     fp.write("\n\n\n\n")
3376     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3377     fp.write("\n")
3378     fp.write(_("                                                Starfleet Command bestows to you\n"))
3379     fp.write("\n")
3380     fp.write("%*s%s\n\n" % (nskip, "", winner))
3381     fp.write(_("                                                           the rank of\n\n"))
3382     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3383     fp.write("                                                          ")
3384     if game.skill ==  SKILL_EXPERT:
3385         fp.write(_(" Expert level\n\n"))
3386     elif game.skill == SKILL_EMERITUS:
3387         fp.write(_("Emeritus level\n\n"))
3388     else:
3389         fp.write(_(" Cheat level\n\n"))
3390     timestring = time.ctime()
3391     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3392              (timestring+4, timestring+20, timestring+11))
3393     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3394     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3395     fp.close()
3396
3397 # Code from io.c begins here
3398
3399 rows = linecount = 0        # for paging
3400 stdscr = None
3401 replayfp = None
3402 fullscreen_window = None
3403 srscan_window     = None   # Short range scan
3404 report_window     = None   # Report legends for status window
3405 status_window     = None   # The status window itself
3406 lrscan_window     = None   # Long range scan
3407 message_window    = None   # Main window for scrolling text
3408 prompt_window     = None   # Prompt window at bottom of display
3409 curwnd = None
3410
3411 def iostart():
3412     global stdscr, rows
3413     # for some recent versions of python2, the following enables UTF8
3414     # for the older ones we probably need to set C locale, and python3
3415     # has no problems at all
3416     if sys.version_info[0] < 3:
3417         locale.setlocale(locale.LC_ALL, "")
3418     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3419     gettext.textdomain("sst")
3420     if not (game.options & OPTION_CURSES):
3421         ln_env = os.getenv("LINES")
3422         if ln_env:
3423             rows = ln_env
3424         else:
3425             rows = 25
3426     else:
3427         stdscr = curses.initscr()
3428         stdscr.keypad(True)
3429         curses.nonl()
3430         curses.cbreak()
3431         if game.options & OPTION_COLOR:
3432             curses.start_color()
3433             curses.use_default_colors()
3434             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3435             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3436             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3437             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3438             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3439             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3440             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3441             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3442         global fullscreen_window, srscan_window, report_window, status_window
3443         global lrscan_window, message_window, prompt_window
3444         (rows, _columns)   = stdscr.getmaxyx()
3445         fullscreen_window = stdscr
3446         srscan_window     = curses.newwin(12, 25, 0,       0)
3447         report_window     = curses.newwin(11, 0,  1,       25)
3448         status_window     = curses.newwin(10, 0,  1,       39)
3449         lrscan_window     = curses.newwin(5,  0,  0,       64)
3450         message_window    = curses.newwin(0,  0,  12,      0)
3451         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3452         message_window.scrollok(True)
3453         setwnd(fullscreen_window)
3454
3455 def ioend():
3456     "Wrap up I/O."
3457     if game.options & OPTION_CURSES:
3458         stdscr.keypad(False)
3459         curses.echo()
3460         curses.nocbreak()
3461         curses.endwin()
3462
3463 def waitfor():
3464     "Wait for user action -- OK to do nothing if on a TTY"
3465     if game.options & OPTION_CURSES:
3466         stdscr.getch()
3467
3468 def announce():
3469     skip(1)
3470     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3471     skip(1)
3472
3473 def pause_game():
3474     if game.skill > SKILL_FAIR:
3475         prompt = _("[CONTINUE?]")
3476     else:
3477         prompt = _("[PRESS ENTER TO CONTINUE]")
3478
3479     if game.options & OPTION_CURSES:
3480         drawmaps(0)
3481         setwnd(prompt_window)
3482         prompt_window.clear()
3483         prompt_window.addstr(prompt)
3484         prompt_window.getstr()
3485         prompt_window.clear()
3486         prompt_window.refresh()
3487         setwnd(message_window)
3488     else:
3489         global linecount
3490         sys.stdout.write('\n')
3491         proutn(prompt)
3492         if not replayfp:
3493             input()
3494         sys.stdout.write('\n' * rows)
3495         linecount = 0
3496
3497 def skip(i):
3498     "Skip i lines.  Pause game if this would cause a scrolling event."
3499     for _dummy in range(i):
3500         if game.options & OPTION_CURSES:
3501             (y, _x) = curwnd.getyx()
3502             try:
3503                 curwnd.move(y+1, 0)
3504             except curses.error:
3505                 pass
3506         else:
3507             global linecount
3508             linecount += 1
3509             if rows and linecount >= rows:
3510                 pause_game()
3511             else:
3512                 sys.stdout.write('\n')
3513
3514 def proutn(proutntline):
3515     "Utter a line with no following line feed."
3516     if game.options & OPTION_CURSES:
3517         (y, x) = curwnd.getyx()
3518         (my, _mx) = curwnd.getmaxyx()
3519         if curwnd == message_window and y >= my - 2:
3520             pause_game()
3521             clrscr()
3522         if logfp and game.cdebug:
3523             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3524         curwnd.addstr(proutntline)
3525         curwnd.refresh()
3526     else:
3527         sys.stdout.write(proutntline)
3528         sys.stdout.flush()
3529
3530 def prout(proutline):
3531     proutn(proutline)
3532     skip(1)
3533
3534 def prouts(proutsline):
3535     "Emit slowly!"
3536     for c in proutsline:
3537         if not replayfp or replayfp.closed:        # Don't slow down replays
3538             time.sleep(0.03)
3539         proutn(c)
3540         if game.options & OPTION_CURSES:
3541             curwnd.refresh()
3542         else:
3543             sys.stdout.flush()
3544     if not replayfp or replayfp.closed:
3545         time.sleep(0.03)
3546
3547 def cgetline():
3548     "Get a line of input."
3549     if game.options & OPTION_CURSES:
3550         linein = codecs.decode(curwnd.getstr()) + "\n"
3551         curwnd.refresh()
3552     else:
3553         if replayfp and not replayfp.closed:
3554             while True:
3555                 linein = replayfp.readline()
3556                 proutn(linein)
3557                 if linein == '':
3558                     prout("*** Replay finished")
3559                     replayfp.close()
3560                     break
3561                 elif linein[0] != "#":
3562                     break
3563         else:
3564             try:
3565                 linein = input() + "\n"
3566             except EOFError:
3567                 prout("")
3568                 sys.exit(0)
3569     if logfp:
3570         logfp.write(linein)
3571     return linein
3572
3573 def setwnd(wnd):
3574     "Change windows -- OK for this to be a no-op in tty mode."
3575     global curwnd
3576     if game.options & OPTION_CURSES:
3577         if game.cdebug and logfp:
3578             if wnd == fullscreen_window:
3579                 legend = "fullscreen"
3580             elif wnd == srscan_window:
3581                 legend = "srscan"
3582             elif wnd == report_window:
3583                 legend = "report"
3584             elif wnd == status_window:
3585                 legend = "status"
3586             elif wnd == lrscan_window:
3587                 legend = "lrscan"
3588             elif wnd == message_window:
3589                 legend = "message"
3590             elif wnd == prompt_window:
3591                 legend = "prompt"
3592             else:
3593                 legend = "unknown"
3594             logfp.write("#curses: setwnd(%s)\n" % legend)
3595         curwnd = wnd
3596         # Some curses implementations get confused when you try this.
3597         try:
3598             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3599         except curses.error:
3600             pass
3601
3602 def clreol():
3603     "Clear to end of line -- can be a no-op in tty mode"
3604     if game.options & OPTION_CURSES:
3605         curwnd.clrtoeol()
3606         curwnd.refresh()
3607
3608 def clrscr():
3609     "Clear screen -- can be a no-op in tty mode."
3610     global linecount
3611     if game.options & OPTION_CURSES:
3612         curwnd.clear()
3613         curwnd.move(0, 0)
3614         curwnd.refresh()
3615     linecount = 0
3616
3617 def textcolor(color=DEFAULT):
3618     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3619         if color == DEFAULT:
3620             curwnd.attrset(0)
3621         elif color ==  BLACK:
3622             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3623         elif color ==  BLUE:
3624             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3625         elif color ==  GREEN:
3626             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3627         elif color ==  CYAN:
3628             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3629         elif color ==  RED:
3630             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3631         elif color ==  MAGENTA:
3632             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3633         elif color ==  BROWN:
3634             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3635         elif color ==  LIGHTGRAY:
3636             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3637         elif color ==  DARKGRAY:
3638             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3639         elif color ==  LIGHTBLUE:
3640             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3641         elif color ==  LIGHTGREEN:
3642             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3643         elif color ==  LIGHTCYAN:
3644             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3645         elif color ==  LIGHTRED:
3646             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3647         elif color ==  LIGHTMAGENTA:
3648             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3649         elif color ==  YELLOW:
3650             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3651         elif color ==  WHITE:
3652             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3653
3654 def highvideo():
3655     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3656         curwnd.attron(curses.A_REVERSE)
3657
3658 #
3659 # Things past this point have policy implications.
3660 #
3661
3662 def drawmaps(mode):
3663     "Hook to be called after moving to redraw maps."
3664     if game.options & OPTION_CURSES:
3665         if mode == 1:
3666             sensor()
3667         setwnd(srscan_window)
3668         curwnd.move(0, 0)
3669         srscan()
3670         if mode != 2:
3671             setwnd(status_window)
3672             status_window.clear()
3673             status_window.move(0, 0)
3674             setwnd(report_window)
3675             report_window.clear()
3676             report_window.move(0, 0)
3677             status()
3678             setwnd(lrscan_window)
3679             lrscan_window.clear()
3680             lrscan_window.move(0, 0)
3681             lrscan(silent=False)
3682
3683 def put_srscan_sym(w, sym):
3684     "Emit symbol for short-range scan."
3685     srscan_window.move(w.i+1, w.j*2+2)
3686     srscan_window.addch(sym)
3687     srscan_window.refresh()
3688
3689 def boom(w):
3690     "Enemy fall down, go boom."
3691     if game.options & OPTION_CURSES:
3692         drawmaps(0)
3693         setwnd(srscan_window)
3694         srscan_window.attron(curses.A_REVERSE)
3695         put_srscan_sym(w, game.quad[w.i][w.j])
3696         #sound(500)
3697         #time.sleep(1.0)
3698         #nosound()
3699         srscan_window.attroff(curses.A_REVERSE)
3700         put_srscan_sym(w, game.quad[w.i][w.j])
3701         curses.delay_output(500)
3702         setwnd(message_window)
3703
3704 def warble():
3705     "Sound and visual effects for teleportation."
3706     if game.options & OPTION_CURSES:
3707         drawmaps(2)
3708         setwnd(message_window)
3709         #sound(50)
3710     prouts("     . . . . .     ")
3711     if game.options & OPTION_CURSES:
3712         #curses.delay_output(1000)
3713         #nosound()
3714         pass
3715
3716 def tracktorpedo(w, step, i, n, iquad):
3717     "Torpedo-track animation."
3718     if not game.options & OPTION_CURSES:
3719         if step == 1:
3720             if n != 1:
3721                 skip(1)
3722                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3723             else:
3724                 skip(1)
3725                 proutn(_("Torpedo track- "))
3726         elif step in {4, 9}:
3727             skip(1)
3728         proutn("%s   " % w)
3729     else:
3730         if not damaged(DSRSENS) or game.condition=="docked":
3731             if i != 0 and step == 1:
3732                 drawmaps(2)
3733                 time.sleep(0.4)
3734             if iquad in {'.', ' '}:
3735                 put_srscan_sym(w, '+')
3736                 #sound(step*10)
3737                 #time.sleep(0.1)
3738                 #nosound()
3739                 put_srscan_sym(w, iquad)
3740             else:
3741                 curwnd.attron(curses.A_REVERSE)
3742                 put_srscan_sym(w, iquad)
3743                 #sound(500)
3744                 #time.sleep(1.0)
3745                 #nosound()
3746                 curwnd.attroff(curses.A_REVERSE)
3747                 put_srscan_sym(w, iquad)
3748         else:
3749             proutn("%s   " % w)
3750
3751 def makechart():
3752     "Display the current galaxy chart."
3753     if game.options & OPTION_CURSES:
3754         setwnd(message_window)
3755         message_window.clear()
3756     chart()
3757     if game.options & OPTION_TTY:
3758         skip(1)
3759
3760 NSYM        = 14
3761
3762 def prstat(txt, data):
3763     proutn(txt)
3764     if game.options & OPTION_CURSES:
3765         skip(1)
3766         setwnd(status_window)
3767     else:
3768         proutn(" " * (NSYM - len(txt)))
3769     proutn(data)
3770     skip(1)
3771     if game.options & OPTION_CURSES:
3772         setwnd(report_window)
3773
3774 # Code from moving.c begins here
3775
3776 def imove(icourse=None, noattack=False):
3777     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3778     w = Coord()
3779
3780     def newquadrant(noattack):
3781         # Leaving quadrant -- allow final enemy attack
3782         # Don't set up attack if being pushed by nova or cloaked
3783         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3784             newcnd()
3785             for enemy in game.enemies:
3786                 finald = (w - enemy.location).distance()
3787                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3788             # Stas Sergeev added the condition
3789             # that attacks only happen if Klingons
3790             # are present and your skill is good.
3791             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3792                 attack(torps_ok=False)
3793             if game.alldone:
3794                 return
3795         # check for edge of galaxy
3796         kinks = 0
3797         while True:
3798             kink = False
3799             if icourse.final.i < 0:
3800                 icourse.final.i = -icourse.final.i
3801                 kink = True
3802             if icourse.final.j < 0:
3803                 icourse.final.j = -icourse.final.j
3804                 kink = True
3805             if icourse.final.i >= GALSIZE*QUADSIZE:
3806                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3807                 kink = True
3808             if icourse.final.j >= GALSIZE*QUADSIZE:
3809                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3810                 kink = True
3811             if kink:
3812                 kinks += 1
3813             else:
3814                 break
3815         if kinks:
3816             game.nkinks += 1
3817             if game.nkinks == 3:
3818                 # Three strikes -- you're out!
3819                 finish(FNEG3)
3820                 return
3821             skip(1)
3822             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3823             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3824             prout(_("YOU WILL BE DESTROYED."))
3825         # Compute final position in new quadrant
3826         if trbeam: # Don't bother if we are to be beamed
3827             return
3828         game.quadrant = icourse.final.quadrant()
3829         game.sector = icourse.final.sector()
3830         skip(1)
3831         prout(_("Entering Quadrant %s.") % game.quadrant)
3832         game.quad[game.sector.i][game.sector.j] = game.ship
3833         newqad()
3834         if game.skill>SKILL_NOVICE:
3835             attack(torps_ok=False)
3836
3837     def check_collision(h):
3838         iquad = game.quad[h.i][h.j]
3839         if iquad != '.':
3840             # object encountered in flight path
3841             stopegy = 50.0*icourse.distance/game.optime
3842             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3843                 for enemy in game.enemies:
3844                     if enemy.location == game.sector:
3845                         collision(rammed=False, enemy=enemy)
3846                         return True
3847                 # This should not happen
3848                 prout(_("Which way did he go?"))
3849                 return False
3850             elif iquad == ' ':
3851                 skip(1)
3852                 prouts(_("***RED ALERT!  RED ALERT!"))
3853                 skip(1)
3854                 proutn("***" + crmshp())
3855                 proutn(_(" pulled into black hole at Sector %s") % h)
3856                 # Getting pulled into a black hole was certain
3857                 # death in Almy's original.  Stas Sergeev added a
3858                 # possibility that you'll get timewarped instead.
3859                 n=0
3860                 for m in range(NDEVICES):
3861                     if game.damage[m]>0:
3862                         n += 1
3863                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3864                 if (game.options & OPTION_BLKHOLE) and rnd.withprob(1-probf):
3865                     timwrp()
3866                 else:
3867                     finish(FHOLE)
3868                 return True
3869             else:
3870                 # something else
3871                 skip(1)
3872                 proutn(crmshp())
3873                 if iquad == '#':
3874                     prout(_(" encounters Tholian web at %s;") % h)
3875                 else:
3876                     prout(_(" blocked by object at %s;") % h)
3877                 proutn(_("Emergency stop required "))
3878                 prout(_("%2d units of energy.") % int(stopegy))
3879                 game.energy -= stopegy
3880                 if game.energy <= 0:
3881                     finish(FNRG)
3882                 return True
3883         return False
3884
3885     trbeam = False
3886     if game.inorbit:
3887         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3888         game.inorbit = False
3889     # If tractor beam is to occur, don't move full distance
3890     if game.state.date+game.optime >= scheduled(FTBEAM):
3891         if game.iscloaked:
3892             # We can't be tractor beamed if cloaked,
3893             # so move the event into the future
3894             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
3895         else:
3896             trbeam = True
3897             game.condition = "red"
3898             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3899             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3900     # Move out
3901     game.quad[game.sector.i][game.sector.j] = '.'
3902     for _m in range(icourse.moves):
3903         icourse.nexttok()
3904         w = icourse.sector()
3905         if icourse.origin.quadrant() != icourse.location.quadrant():
3906             newquadrant(noattack)
3907             break
3908         elif check_collision(w):
3909             prout(_("Collision detected"))
3910             break
3911         else:
3912             game.sector = w
3913     # We're in destination quadrant -- compute new average enemy distances
3914     game.quad[game.sector.i][game.sector.j] = game.ship
3915     if game.enemies:
3916         for enemy in game.enemies:
3917             finald = (w-enemy.location).distance()
3918             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3919             enemy.kdist = finald
3920         sortenemies()
3921         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3922             attack(torps_ok=False)
3923         for enemy in game.enemies:
3924             enemy.kavgd = enemy.kdist
3925     newcnd()
3926     drawmaps(0)
3927     setwnd(message_window)
3928
3929 def dock(verbose):
3930     "Dock our ship at a starbase."
3931     scanner.chew()
3932     if game.condition == "docked" and verbose:
3933         prout(_("Already docked."))
3934         return
3935     if game.inorbit:
3936         prout(_("You must first leave standard orbit."))
3937         return
3938     if game.base is None or not game.base.valid_sector():
3939         prout(_("No starbase available for docking in this quadrant."))
3940         return
3941     if (abs(game.sector.i-game.base.i) > 1) or (abs(game.sector.j-game.base.j) > 1):
3942         prout(crmshp() + _(" not adjacent to base."))
3943         return
3944     if game.iscloaked:
3945         prout(_("You cannot dock while cloaked."))
3946         return
3947     game.condition = "docked"
3948     if verbose:
3949         prout(_("Docked."))
3950     game.ididit = True
3951     if game.energy < game.inenrg:
3952         game.energy = game.inenrg
3953     game.shield = game.inshld
3954     game.torps = game.intorps
3955     game.lsupres = game.inlsr
3956     game.state.crew = FULLCREW
3957     if game.brigcapacity-game.brigfree > 0:
3958         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3959         game.kcaptured += game.brigcapacity-game.brigfree
3960         game.brigfree = game.brigcapacity
3961     if communicating() and \
3962         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3963         # get attack report from base
3964         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3965         attackreport(False)
3966         game.iseenit = True
3967
3968 def cartesian(loc1=None, loc2=None):
3969     if loc1 is None:
3970         return game.quadrant * QUADSIZE + game.sector
3971     elif loc2 is None:
3972         return game.quadrant * QUADSIZE + loc1
3973     else:
3974         return loc1 * QUADSIZE + loc2
3975
3976 def getcourse(isprobe):
3977     "Get a course and distance from the user."
3978     key = ""
3979     dquad = copy.copy(game.quadrant)
3980     navmode = "unspecified"
3981     itemp = "curt"
3982     dsect = Coord()
3983     iprompt = False
3984     if game.landed and not isprobe:
3985         prout(_("Dummy! You can't leave standard orbit until you"))
3986         proutn(_("are back aboard the ship."))
3987         scanner.chew()
3988         raise TrekError
3989     while navmode == "unspecified":
3990         if damaged(DNAVSYS):
3991             if isprobe:
3992                 prout(_("Computer damaged; manual navigation only"))
3993             else:
3994                 prout(_("Computer damaged; manual movement only"))
3995             scanner.chew()
3996             navmode = "manual"
3997             key = "IHEOL"
3998             break
3999         key = scanner.nexttok()
4000         if key == "IHEOL":
4001             proutn(_("Manual or automatic- "))
4002             iprompt = True
4003             scanner.chew()
4004         elif key == "IHALPHA":
4005             if scanner.sees("manual"):
4006                 navmode = "manual"
4007                 key = scanner.nexttok()
4008                 break
4009             elif scanner.sees("automatic"):
4010                 navmode = "automatic"
4011                 key = scanner.nexttok()
4012                 break
4013             else:
4014                 huh()
4015                 scanner.chew()
4016                 raise TrekError
4017         else: # numeric
4018             if isprobe:
4019                 prout(_("(Manual navigation assumed.)"))
4020             else:
4021                 prout(_("(Manual movement assumed.)"))
4022             navmode = "manual"
4023             break
4024     delta = Coord()
4025     if navmode == "automatic":
4026         while key == "IHEOL":
4027             if isprobe:
4028                 proutn(_("Target quadrant or quadrant&sector- "))
4029             else:
4030                 proutn(_("Destination sector or quadrant&sector- "))
4031             scanner.chew()
4032             iprompt = True
4033             key = scanner.nexttok()
4034         scanner.push(scanner.token)     # Something IHREAL or IHALPHA awaits us
4035         first = scanner.getcoord()
4036         if first is None:
4037             raise TrekError
4038         scanner.nexttok()
4039         if scanner.type == "IHEOL":
4040             second = None
4041         else:
4042             scanner.push(scanner.token)
4043             second = scanner.getcoord()
4044             if second is None:
4045                 raise TrekError
4046         scanner.chew()
4047         if second is not None:
4048             dquad.i = first.i
4049             dquad.j = first.j
4050             dsect.i = second.i
4051             dsect.j = second.j
4052         else:
4053             # only one pair of numbers was specified
4054             if isprobe:
4055                 # only quadrant specified -- go to center of dest quad
4056                 dquad.i = first.i
4057                 dquad.j = first.j
4058                 dsect.j = dsect.i = (QUADSIZE/2)-1        # preserves 1-origin behavior
4059             else:
4060                 # only sector specified
4061                 dsect.i = first.i
4062                 dsect.j = first.j
4063             itemp = "normal"
4064         if not dquad.valid_quadrant() or not dsect.valid_sector():
4065             huh()
4066             raise TrekError
4067         skip(1)
4068         if not isprobe:
4069             if itemp > "curt":
4070                 if iprompt:
4071                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
4072             else:
4073                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
4074         # the actual deltas get computed here
4075         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
4076         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
4077     else: # manual
4078         while key == "IHEOL":
4079             proutn(_("X and Y displacements- "))
4080             scanner.chew()
4081             iprompt = True
4082             key = scanner.nexttok()
4083         itemp = "verbose"
4084         if key == "IHREAL":
4085             delta.j = scanner.real
4086         else:
4087             huh()
4088             raise TrekError
4089         key = scanner.nexttok()
4090         if key == "IHREAL":
4091             delta.i = scanner.real
4092         elif key == "IHEOL":
4093             delta.i = 0
4094             scanner.push("\n")
4095         else:
4096             huh()
4097             raise TrekError
4098     # Check for zero movement
4099     if delta.i == 0 and delta.j == 0:
4100         scanner.chew()
4101         raise TrekError
4102     if itemp == "verbose" and not isprobe:
4103         skip(1)
4104         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4105     scanner.chew()
4106     return course(bearing=delta.bearing(), distance=delta.distance())
4107
4108 class course:
4109     def __init__(self, bearing, distance, origin=None):
4110         self.distance = distance
4111         self.bearing = bearing
4112         if origin is None:
4113             self.origin = cartesian(game.quadrant, game.sector)
4114         else:
4115             self.origin = origin
4116         # The bearing() code we inherited from FORTRAN is actually computing
4117         # clockface directions!
4118         if self.bearing < 0.0:
4119             self.bearing += 12.0
4120         self.angle = ((15.0 - self.bearing) * 0.5235988)
4121         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4122         bigger = max(abs(self.increment.i), abs(self.increment.j))
4123         self.increment /= bigger
4124         self.moves = int(round(10*self.distance*bigger))
4125         self.reset()
4126         self.final = (self.location + self.moves*self.increment).roundtogrid()
4127         self.location = self.origin
4128         self.nextlocation = None
4129     def reset(self):
4130         self.location = self.origin
4131         self.step = 0
4132     def arrived(self):
4133         return self.location.roundtogrid() == self.final
4134     def nexttok(self):
4135         "Next step on course."
4136         self.step += 1
4137         self.nextlocation = self.location + self.increment
4138         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4139         self.location = self.nextlocation
4140         return samequad
4141     def quadrant(self):
4142         return self.location.quadrant()
4143     def sector(self):
4144         return self.location.sector()
4145     def power(self, w):
4146         return self.distance*(w**3)*(game.shldup+1)
4147     def time(self, w):
4148         return 10.0*self.distance/w**2
4149
4150 def impulse():
4151     "Move under impulse power."
4152     game.ididit = False
4153     if damaged(DIMPULS):
4154         scanner.chew()
4155         skip(1)
4156         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4157         return
4158     if game.energy > 30.0:
4159         try:
4160             icourse = getcourse(isprobe=False)
4161         except TrekError:
4162             return
4163         power = 20.0 + 100.0*icourse.distance
4164     else:
4165         power = 30.0
4166     if power >= game.energy:
4167         # Insufficient power for trip
4168         skip(1)
4169         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4170         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4171         if game.energy > 30:
4172             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4173                    int(0.01 * (game.energy-20.0)-0.05))
4174             prout(_(" quadrants.\""))
4175         else:
4176             prout(_("quadrant.  They are, therefore, useless.\""))
4177         scanner.chew()
4178         return
4179     # Make sure enough time is left for the trip
4180     game.optime = icourse.distance/0.095
4181     if game.optime >= game.state.remtime:
4182         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4183         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4184         proutn(_("we dare spend the time?\" "))
4185         if not ja():
4186             return
4187     # Activate impulse engines and pay the cost
4188     imove(icourse, noattack=False)
4189     game.ididit = True
4190     if game.alldone:
4191         return
4192     power = 20.0 + 100.0*icourse.distance
4193     game.energy -= power
4194     game.optime = icourse.distance/0.095
4195     if game.energy <= 0:
4196         finish(FNRG)
4197     return
4198
4199 def warp(wcourse, involuntary):
4200     "ove under warp drive."
4201     blooey = False; twarp = False
4202     if not involuntary: # Not WARPX entry
4203         game.ididit = False
4204         if game.iscloaked:
4205             scanner.chew()
4206             skip(1)
4207             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4208             return
4209         if game.damage[DWARPEN] > 10.0:
4210             scanner.chew()
4211             skip(1)
4212             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4213             return
4214         if damaged(DWARPEN) and game.warpfac > 4.0:
4215             scanner.chew()
4216             skip(1)
4217             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4218             prout(_("  is repaired, I can only give you warp 4.\""))
4219             return
4220                # Read in course and distance
4221         if wcourse is None:
4222             try:
4223                 wcourse = getcourse(isprobe=False)
4224             except TrekError:
4225                 return
4226         # Make sure starship has enough energy for the trip
4227         # Note: this formula is slightly different from the C version,
4228         # and lets you skate a bit closer to the edge.
4229         if wcourse.power(game.warpfac) >= game.energy:
4230             # Insufficient power for trip
4231             game.ididit = False
4232             skip(1)
4233             prout(_("Engineering to bridge--"))
4234             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4235                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4236                 if iwarp <= 0:
4237                     prout(_("We can't do it, Captain. We don't have enough energy."))
4238                 else:
4239                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4240                     if game.shldup:
4241                         prout(",")
4242                         prout(_("if you'll lower the shields."))
4243                     else:
4244                         prout(".")
4245             else:
4246                 prout(_("We haven't the energy to go that far with the shields up."))
4247             return
4248         # Make sure enough time is left for the trip
4249         game.optime = wcourse.time(game.warpfac)
4250         if game.optime >= 0.8*game.state.remtime:
4251             skip(1)
4252             prout(_("First Officer Spock- \"Captain, I compute that such"))
4253             proutn(_("  a trip would require approximately %2.0f") %
4254                    (100.0*game.optime/game.state.remtime))
4255             prout(_(" percent of our"))
4256             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4257             if not ja():
4258                 game.ididit = False
4259                 game.optime=0
4260                 return
4261     # Entry WARPX
4262     if game.warpfac > 6.0:
4263         # Decide if engine damage will occur
4264         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4265         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4266         if prob > rnd.real():
4267             blooey = True
4268             wcourse.distance = rnd.real(wcourse.distance)
4269         # Decide if time warp will occur
4270         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > rnd.real():
4271             twarp = True
4272         if game.idebug and game.warpfac==10 and not twarp:
4273             blooey = False
4274             proutn("=== Force time warp? ")
4275             if ja():
4276                 twarp = True
4277         if blooey or twarp:
4278             # If time warp or engine damage, check path
4279             # If it is obstructed, don't do warp or damage
4280             look = wcourse.moves
4281             while look > 0:
4282                 look -= 1
4283                 wcourse.nexttok()
4284                 w = wcourse.sector()
4285                 if not w.valid_sector():
4286                     break
4287                 if game.quad[w.i][w.j] != '.':
4288                     blooey = False
4289                     twarp = False
4290             wcourse.reset()
4291     # Activate Warp Engines and pay the cost
4292     imove(wcourse, noattack=False)
4293     if game.alldone:
4294         return
4295     game.energy -= wcourse.power(game.warpfac)
4296     if game.energy <= 0:
4297         finish(FNRG)
4298     game.optime = wcourse.time(game.warpfac)
4299     if twarp:
4300         timwrp()
4301     if blooey:
4302         game.damage[DWARPEN] = game.damfac * rnd.real(1.0, 4.0)
4303         skip(1)
4304         prout(_("Engineering to bridge--"))
4305         prout(_("  Scott here.  The warp engines are damaged."))
4306         prout(_("  We'll have to reduce speed to warp 4."))
4307     game.ididit = True
4308     return
4309
4310 def setwarp():
4311     "Change the warp factor."
4312     while True:
4313         key=scanner.nexttok()
4314         if key != "IHEOL":
4315             break
4316         scanner.chew()
4317         proutn(_("Warp factor- "))
4318     if key != "IHREAL":
4319         huh()
4320         return
4321     if game.damage[DWARPEN] > 10.0:
4322         prout(_("Warp engines inoperative."))
4323         return
4324     if damaged(DWARPEN) and scanner.real > 4.0:
4325         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4326         prout(_("  but right now we can only go warp 4.\""))
4327         return
4328     if scanner.real > 10.0:
4329         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4330         return
4331     if scanner.real < 1.0:
4332         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4333         return
4334     oldfac = game.warpfac
4335     game.warpfac = scanner.real
4336     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4337         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4338               int(game.warpfac))
4339         return
4340     if game.warpfac < 8.00:
4341         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4342         return
4343     if game.warpfac == 10.0:
4344         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4345         return
4346     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4347     return
4348
4349 def atover(igrab):
4350     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4351     scanner.chew()
4352     # is captain on planet?
4353     if game.landed:
4354         if damaged(DTRANSP):
4355             finish(FPNOVA)
4356             return
4357         prout(_("Scotty rushes to the transporter controls."))
4358         if game.shldup:
4359             prout(_("But with the shields up it's hopeless."))
4360             finish(FPNOVA)
4361         prouts(_("His desperate attempt to rescue you . . ."))
4362         if rnd.withprob(0.5):
4363             prout(_("fails."))
4364             finish(FPNOVA)
4365             return
4366         prout(_("SUCCEEDS!"))
4367         if game.imine:
4368             game.imine = False
4369             proutn(_("The crystals mined were "))
4370             if rnd.withprob(0.25):
4371                 prout(_("lost."))
4372             else:
4373                 prout(_("saved."))
4374                 game.icrystl = True
4375     if igrab:
4376         return
4377     # Check to see if captain in shuttle craft
4378     if game.icraft:
4379         finish(FSTRACTOR)
4380     if game.alldone:
4381         return
4382     # Inform captain of attempt to reach safety
4383     skip(1)
4384     while True:
4385         if game.justin:
4386             prouts(_("***RED ALERT!  RED ALERT!"))
4387             skip(1)
4388             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4389             prouts(_("   a supernova."))
4390             skip(2)
4391         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4392         prout(_("safely out of quadrant."))
4393         if not damaged(DRADIO):
4394             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4395         # Try to use warp engines
4396         if damaged(DWARPEN):
4397             skip(1)
4398             prout(_("Warp engines damaged."))
4399             finish(FSNOVAED)
4400             return
4401         game.warpfac = rnd.real(6.0, 8.0)
4402         prout(_("Warp factor set to %d") % int(game.warpfac))
4403         power = 0.75*game.energy
4404         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4405         dist = max(dist, rnd.real(math.sqrt(2)))
4406         bugout = course(bearing=rnd.real(12), distance=dist)        # How dumb!
4407         game.optime = bugout.time(game.warpfac)
4408         game.justin = False
4409         game.inorbit = False
4410         warp(bugout, involuntary=True)
4411         if not game.justin:
4412             # This is bad news, we didn't leave quadrant.
4413             if game.alldone:
4414                 return
4415             skip(1)
4416             prout(_("Insufficient energy to leave quadrant."))
4417             finish(FSNOVAED)
4418             return
4419         # Repeat if another snova
4420         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4421             break
4422     if game.unwon()==0:
4423         finish(FWON) # Snova killed remaining enemy.
4424
4425 def timwrp():
4426     "Let's do the time warp again."
4427     prout(_("***TIME WARP ENTERED."))
4428     if game.state.snap and rnd.withprob(0.5):
4429         # Go back in time
4430         prout(_("You are traveling backwards in time %d stardates.") %
4431               int(game.state.date-game.snapsht.date))
4432         game.state = game.snapsht
4433         game.state.snap = False
4434         if len(game.state.kcmdr):
4435             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4436             schedule(FBATTAK, expran(0.3*game.intime))
4437         schedule(FSNOVA, expran(0.5*game.intime))
4438         # next snapshot will be sooner
4439         schedule(FSNAP, expran(0.25*game.state.remtime))
4440
4441         if game.state.nscrem:
4442             schedule(FSCMOVE, 0.2777)
4443         game.isatb = 0
4444         unschedule(FCDBAS)
4445         unschedule(FSCDBAS)
4446         game.battle.invalidate()
4447         # Make sure Galileo is consistant -- Snapshot may have been taken
4448         # when on planet, which would give us two Galileos!
4449         gotit = False
4450         for l in range(game.inplan):
4451             if game.state.planets[l].known == "shuttle_down":
4452                 gotit = True
4453                 if game.iscraft == "onship" and game.ship=='E':
4454                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4455                     game.iscraft = "offship"
4456         # Likewise, if in the original time the Galileo was abandoned, but
4457         # was on ship earlier, it would have vanished -- let's restore it.
4458         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4459             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4460             game.iscraft = "onship"
4461         # There used to be code to do the actual reconstrction here,
4462         # but the starchart is now part of the snapshotted galaxy state.
4463         prout(_("Spock has reconstructed a correct star chart from memory"))
4464     else:
4465         # Go forward in time
4466         game.optime = expran(0.5*game.intime)
4467         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4468         # cheat to make sure no tractor beams occur during time warp
4469         postpone(FTBEAM, game.optime)
4470         game.damage[DRADIO] += game.optime
4471     newqad()
4472     events()        # Stas Sergeev added this -- do pending events
4473
4474 def probe():
4475     "Launch deep-space probe."
4476     # New code to launch a deep space probe
4477     if game.nprobes == 0:
4478         scanner.chew()
4479         skip(1)
4480         if game.ship == 'E':
4481             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4482         else:
4483             prout(_("Ye Faerie Queene has no deep space probes."))
4484         return
4485     if damaged(DDSP):
4486         scanner.chew()
4487         skip(1)
4488         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4489         return
4490     if is_scheduled(FDSPROB):
4491         scanner.chew()
4492         skip(1)
4493         if damaged(DRADIO) and game.condition != "docked":
4494             prout(_("Spock-  \"Records show the previous probe has not yet"))
4495             prout(_("   reached its destination.\""))
4496         else:
4497             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4498         return
4499     key = scanner.nexttok()
4500     if key == "IHEOL":
4501         if game.nprobes == 1:
4502             prout(_("1 probe left."))
4503         else:
4504             prout(_("%d probes left") % game.nprobes)
4505         proutn(_("Are you sure you want to fire a probe? "))
4506         if not ja():
4507             return
4508     game.isarmed = False
4509     if key == "IHALPHA" and scanner.token == "armed":
4510         game.isarmed = True
4511         key = scanner.nexttok()
4512     elif key == "IHEOL":
4513         proutn(_("Arm NOVAMAX warhead? "))
4514         game.isarmed = ja()
4515     elif key == "IHREAL":                # first element of course
4516         scanner.push(scanner.token)
4517     try:
4518         game.probe = getcourse(isprobe=True)
4519     except TrekError:
4520         return
4521     game.nprobes -= 1
4522     schedule(FDSPROB, 0.01) # Time to move one sector
4523     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4524     game.ididit = True
4525     return
4526
4527 def mayday():
4528     "Yell for help from nearest starbase."
4529     # There's more than one way to move in this game!
4530     scanner.chew()
4531     # Test for conditions which prevent calling for help
4532     if game.condition == "docked":
4533         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4534         return
4535     if damaged(DRADIO):
4536         prout(_("Subspace radio damaged."))
4537         return
4538     if not game.state.baseq:
4539         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4540         return
4541     if game.landed:
4542         prout(_("You must be aboard the %s.") % crmshp())
4543         return
4544     # OK -- call for help from nearest starbase
4545     game.nhelp += 1
4546     if game.base.i!=0:
4547         # There's one in this quadrant
4548         ddist = (game.base - game.sector).distance()
4549     else:
4550         ibq = None      # Force base-quadrant game to persist past loop
4551         ddist = FOREVER
4552         for ibq in game.state.baseq:
4553             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4554             if xdist < ddist:
4555                 ddist = xdist
4556         if ibq is None:
4557             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4558             return
4559         # Since starbase not in quadrant, set up new quadrant
4560         game.quadrant = ibq
4561         newqad()
4562     # dematerialize starship
4563     game.quad[game.sector.i][game.sector.j]='.'
4564     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4565            % (game.quadrant, crmshp()))
4566     game.sector.invalidate()
4567     for m in range(1, 5+1):
4568         w = game.base.scatter()
4569         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4570             # found one -- finish up
4571             game.sector = w
4572             break
4573     if game.sector is None:
4574         prout(_("You have been lost in space..."))
4575         finish(FMATERIALIZE)
4576         return
4577     # Give starbase three chances to rematerialize starship
4578     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4579     for m in range(1, 3+1):
4580         if m == 1: proutn(_("1st"))
4581         elif m == 2: proutn(_("2nd"))
4582         elif m == 3: proutn(_("3rd"))
4583         proutn(_(" attempt to re-materialize ") + crmshp())
4584         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4585         textcolor(RED)
4586         warble()
4587         if rnd.real() > probf:
4588             break
4589         prout(_("fails."))
4590         textcolor(DEFAULT)
4591         if game.options & OPTION_CURSES:
4592             curses.delay_output(500)
4593     if m > 3:
4594         game.quad[game.sector.i][game.sector.j]='?'
4595         game.alive = False
4596         drawmaps(1)
4597         setwnd(message_window)
4598         finish(FMATERIALIZE)
4599         return
4600     game.quad[game.sector.i][game.sector.j]=game.ship
4601     textcolor(GREEN)
4602     prout(_("succeeds."))
4603     textcolor(DEFAULT)
4604     dock(False)
4605     skip(1)
4606     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4607
4608 def abandon():
4609     "Abandon ship."
4610     scanner.chew()
4611     if game.condition=="docked":
4612         if game.ship!='E':
4613             prout(_("You cannot abandon Ye Faerie Queene."))
4614             return
4615     else:
4616         # Must take shuttle craft to exit
4617         if game.damage[DSHUTTL]==-1:
4618             prout(_("Ye Faerie Queene has no shuttle craft."))
4619             return
4620         if game.damage[DSHUTTL]<0:
4621             prout(_("Shuttle craft now serving Big Macs."))
4622             return
4623         if game.damage[DSHUTTL]>0:
4624             prout(_("Shuttle craft damaged."))
4625             return
4626         if game.landed:
4627             prout(_("You must be aboard the ship."))
4628             return
4629         if game.iscraft != "onship":
4630             prout(_("Shuttle craft not currently available."))
4631             return
4632         # Emit abandon ship messages
4633         skip(1)
4634         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4635         skip(1)
4636         prouts(_("***ALL HANDS ABANDON SHIP!"))
4637         skip(2)
4638         prout(_("Captain and crew escape in shuttle craft."))
4639         if not game.state.baseq:
4640             # Oops! no place to go...
4641             finish(FABANDN)
4642             return
4643         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4644         # Dispose of crew
4645         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4646             prout(_("Remainder of ship's complement beam down"))
4647             prout(_("to nearest habitable planet."))
4648         elif q.planet is not None and not damaged(DTRANSP):
4649             prout(_("Remainder of ship's complement beam down to %s.") %
4650                   q.planet)
4651         else:
4652             prout(_("Entire crew of %d left to die in outer space.") %
4653                   game.state.crew)
4654             game.casual += game.state.crew
4655             game.abandoned += game.state.crew
4656         # If at least one base left, give 'em the Faerie Queene
4657         skip(1)
4658         game.icrystl = False # crystals are lost
4659         game.nprobes = 0 # No probes
4660         prout(_("You are captured by Klingons and released to"))
4661         prout(_("the Federation in a prisoner-of-war exchange."))
4662         nb = rnd.integer(len(game.state.baseq))
4663         # Set up quadrant and position FQ adjacient to base
4664         if not game.quadrant == game.state.baseq[nb]:
4665             game.quadrant = game.state.baseq[nb]
4666             game.sector.i = game.sector.j = 5
4667             newqad()
4668         while True:
4669             # position next to base by trial and error
4670             game.quad[game.sector.i][game.sector.j] = '.'
4671             l = QUADSIZE
4672             for l in range(QUADSIZE):
4673                 game.sector = game.base.scatter()
4674                 if game.sector.valid_sector() and \
4675                        game.quad[game.sector.i][game.sector.j] == '.':
4676                     break
4677             if l < QUADSIZE:
4678                 break # found a spot
4679             game.sector.i=QUADSIZE/2
4680             game.sector.j=QUADSIZE/2
4681             newqad()
4682     # Get new commission
4683     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4684     game.state.crew = FULLCREW
4685     prout(_("Starfleet puts you in command of another ship,"))
4686     prout(_("the Faerie Queene, which is antiquated but,"))
4687     prout(_("still useable."))
4688     if game.icrystl:
4689         prout(_("The dilithium crystals have been moved."))
4690     game.imine = False
4691     game.iscraft = "offship" # Galileo disappears
4692     # Resupply ship
4693     game.condition="docked"
4694     for l in range(NDEVICES):
4695         game.damage[l] = 0.0
4696     game.damage[DSHUTTL] = -1
4697     game.energy = game.inenrg = 3000.0
4698     game.shield = game.inshld = 1250.0
4699     game.torps = game.intorps = 6
4700     game.lsupres=game.inlsr=3.0
4701     game.shldup=False
4702     game.warpfac=5.0
4703     game.brigfree = game.brigcapacity = 300
4704     return
4705
4706 # Code from planets.c begins here.
4707
4708 def consumeTime():
4709     "Abort a lengthy operation if an event interrupts it."
4710     game.ididit = True
4711     events()
4712     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4713         return True
4714     return False
4715
4716 def survey():
4717     "Report on (uninhabited) planets in the galaxy."
4718     iknow = False
4719     skip(1)
4720     scanner.chew()
4721     prout(_("Spock-  \"Planet report follows, Captain.\""))
4722     skip(1)
4723     for i in range(game.inplan):
4724         if game.state.planets[i].pclass == "destroyed":
4725             continue
4726         if (game.state.planets[i].known != "unknown" \
4727             and not game.state.planets[i].inhabited) \
4728             or game.idebug:
4729             iknow = True
4730             if game.idebug and game.state.planets[i].known=="unknown":
4731                 proutn("(Unknown) ")
4732             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4733             proutn(_("   class "))
4734             proutn(game.state.planets[i].pclass)
4735             proutn("   ")
4736             if game.state.planets[i].crystals != "present":
4737                 proutn(_("no "))
4738             prout(_("dilithium crystals present."))
4739             if game.state.planets[i].known=="shuttle_down":
4740                 prout(_("    Shuttle Craft Galileo on surface."))
4741     if not iknow:
4742         prout(_("No information available."))
4743
4744 def orbit():
4745     "Enter standard orbit."
4746     skip(1)
4747     scanner.chew()
4748     if game.inorbit:
4749         prout(_("Already in standard orbit."))
4750         return
4751     if damaged(DWARPEN) and damaged(DIMPULS):
4752         prout(_("Both warp and impulse engines damaged."))
4753         return
4754     if game.plnet is None:
4755         prout("There is no planet in this sector.")
4756         return
4757     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4758         prout(crmshp() + _(" not adjacent to planet."))
4759         skip(1)
4760         return
4761     game.optime = rnd.real(0.02, 0.05)
4762     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4763     newcnd()
4764     if consumeTime():
4765         return
4766     game.height = rnd.real(1400, 8600)
4767     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4768     game.inorbit = True
4769     game.ididit = True
4770
4771 def sensor():
4772     "Examine planets in this quadrant."
4773     if damaged(DSRSENS):
4774         if game.options & OPTION_TTY:
4775             prout(_("Short range sensors damaged."))
4776         return
4777     if game.iplnet is None:
4778         if game.options & OPTION_TTY:
4779             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4780         return
4781     if game.iplnet.known == "unknown":
4782         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4783         skip(1)
4784         prout(_("         Planet at Sector %s is of class %s.") %
4785               (game.plnet, game.iplnet.pclass))
4786         if game.iplnet.known=="shuttle_down":
4787             prout(_("         Sensors show Galileo still on surface."))
4788         proutn(_("         Readings indicate"))
4789         if game.iplnet.crystals != "present":
4790             proutn(_(" no"))
4791         prout(_(" dilithium crystals present.\""))
4792         if game.iplnet.known == "unknown":
4793             game.iplnet.known = "known"
4794     elif game.iplnet.inhabited:
4795         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4796         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4797
4798 def beam():
4799     "Use the transporter."
4800     nrgneed = 0
4801     scanner.chew()
4802     skip(1)
4803     if damaged(DTRANSP):
4804         prout(_("Transporter damaged."))
4805         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4806             skip(1)
4807             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4808             if ja():
4809                 shuttle()
4810         return
4811     if not game.inorbit:
4812         prout(crmshp() + _(" not in standard orbit."))
4813         return
4814     if game.shldup:
4815         prout(_("Impossible to transport through shields."))
4816         return
4817     if game.iplnet.known=="unknown":
4818         prout(_("Spock-  \"Captain, we have no information on this planet"))
4819         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4820         prout(_("  you may not go down.\""))
4821         return
4822     if not game.landed and game.iplnet.crystals=="absent":
4823         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4824         prout(_("  exploring a planet with no dilithium crystals."))
4825         proutn(_("  Are you sure this is wise?\" "))
4826         if not ja():
4827             scanner.chew()
4828             return
4829     if not (game.options & OPTION_PLAIN):
4830         nrgneed = 50 * game.skill + game.height / 100.0
4831         if nrgneed > game.energy:
4832             prout(_("Engineering to bridge--"))
4833             prout(_("  Captain, we don't have enough energy for transportation."))
4834             return
4835         if not game.landed and nrgneed * 2 > game.energy:
4836             prout(_("Engineering to bridge--"))
4837             prout(_("  Captain, we have enough energy only to transport you down to"))
4838             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4839             if game.iplnet.known == "shuttle_down":
4840                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4841             proutn(_("  Are you sure this is wise?\" "))
4842             if not ja():
4843                 scanner.chew()
4844                 return
4845     if game.landed:
4846         # Coming from planet
4847         if game.iplnet.known=="shuttle_down":
4848             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4849             if ja():
4850                 scanner.chew()
4851                 return
4852             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4853         prout(_("Landing party assembled, ready to beam up."))
4854         skip(1)
4855         prout(_("Kirk whips out communicator..."))
4856         prouts(_("BEEP  BEEP  BEEP"))
4857         skip(2)
4858         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4859     else:
4860         # Going to planet
4861         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4862         skip(1)
4863         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4864         skip(1)
4865         prout(_("Kirk-  \"Energize.\""))
4866     game.ididit = True
4867     skip(1)
4868     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4869     skip(2)
4870     if not rnd.withprob(0.98):
4871         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4872         skip(2)
4873         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4874         finish(FLOST)
4875         return
4876     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4877     game.landed = not game.landed
4878     game.energy -= nrgneed
4879     skip(2)
4880     prout(_("Transport complete."))
4881     if game.landed and game.iplnet.known=="shuttle_down":
4882         prout(_("The shuttle craft Galileo is here!"))
4883     if not game.landed and game.imine:
4884         game.icrystl = True
4885         game.cryprob = 0.05
4886     game.imine = False
4887     return
4888
4889 def mine():
4890     "Strip-mine a world for dilithium."
4891     skip(1)
4892     scanner.chew()
4893     if not game.landed:
4894         prout(_("Mining party not on planet."))
4895         return
4896     if game.iplnet.crystals == "mined":
4897         prout(_("This planet has already been strip-mined for dilithium."))
4898         return
4899     elif game.iplnet.crystals == "absent":
4900         prout(_("No dilithium crystals on this planet."))
4901         return
4902     if game.imine:
4903         prout(_("You've already mined enough crystals for this trip."))
4904         return
4905     if game.icrystl and game.cryprob == 0.05:
4906         prout(_("With all those fresh crystals aboard the ") + crmshp())
4907         prout(_("there's no reason to mine more at this time."))
4908         return
4909     game.optime = rnd.real(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4910     if consumeTime():
4911         return
4912     prout(_("Mining operation complete."))
4913     game.iplnet.crystals = "mined"
4914     game.imine = game.ididit = True
4915
4916 def usecrystals():
4917     "Use dilithium crystals."
4918     game.ididit = False
4919     skip(1)
4920     scanner.chew()
4921     if not game.icrystl:
4922         prout(_("No dilithium crystals available."))
4923         return
4924     if game.energy >= 1000:
4925         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4926         prout(_("  except when Condition Yellow exists."))
4927         return
4928     prout(_("Spock- \"Captain, I must warn you that loading"))
4929     prout(_("  raw dilithium crystals into the ship's power"))
4930     prout(_("  system may risk a severe explosion."))
4931     proutn(_("  Are you sure this is wise?\" "))
4932     if not ja():
4933         scanner.chew()
4934         return
4935     skip(1)
4936     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4937     prout(_("  Mr. Spock and I will try it.\""))
4938     skip(1)
4939     prout(_("Spock-  \"Crystals in place, Sir."))
4940     prout(_("  Ready to activate circuit.\""))
4941     skip(1)
4942     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4943     skip(1)
4944     if rnd.withprob(game.cryprob):
4945         prouts(_("  \"Activating now! - - No good!  It's***"))
4946         skip(2)
4947         prouts(_("***RED ALERT!  RED A*L********************************"))
4948         skip(1)
4949         stars()
4950         prouts(_("******************   KA-BOOM!!!!   *******************"))
4951         skip(1)
4952         kaboom()
4953         return
4954     game.energy += rnd.real(5000.0, 5500.0)
4955     prouts(_("  \"Activating now! - - "))
4956     prout(_("The instruments"))
4957     prout(_("   are going crazy, but I think it's"))
4958     prout(_("   going to work!!  Congratulations, Sir!\""))
4959     game.cryprob *= 2.0
4960     game.ididit = True
4961
4962 def shuttle():
4963     "Use shuttlecraft for planetary jaunt."
4964     scanner.chew()
4965     skip(1)
4966     if damaged(DSHUTTL):
4967         if game.damage[DSHUTTL] == -1.0:
4968             if game.inorbit and game.iplnet.known == "shuttle_down":
4969                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4970             else:
4971                 prout(_("Ye Faerie Queene had no shuttle craft."))
4972         elif game.damage[DSHUTTL] > 0:
4973             prout(_("The Galileo is damaged."))
4974         else: # game.damage[DSHUTTL] < 0
4975             prout(_("Shuttle craft is now serving Big Macs."))
4976         return
4977     if not game.inorbit:
4978         prout(crmshp() + _(" not in standard orbit."))
4979         return
4980     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4981         prout(_("Shuttle craft not currently available."))
4982         return
4983     if not game.landed and game.iplnet.known=="shuttle_down":
4984         prout(_("You will have to beam down to retrieve the shuttle craft."))
4985         return
4986     if game.shldup or game.condition == "docked":
4987         prout(_("Shuttle craft cannot pass through shields."))
4988         return
4989     if game.iplnet.known=="unknown":
4990         prout(_("Spock-  \"Captain, we have no information on this planet"))
4991         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4992         prout(_("  you may not fly down.\""))
4993         return
4994     game.optime = 3.0e-5*game.height
4995     if game.optime >= 0.8*game.state.remtime:
4996         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4997         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4998                int(100*game.optime/game.state.remtime))
4999         prout(_("remaining time."))
5000         proutn(_("Are you sure this is wise?\" "))
5001         if not ja():
5002             game.optime = 0.0
5003             return
5004     if game.landed:
5005         # Kirk on planet
5006         if game.iscraft == "onship":
5007             # Galileo on ship!
5008             if not damaged(DTRANSP):
5009                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
5010                 if ja():
5011                     beam()
5012                     return
5013                 proutn(_("Shuttle crew"))
5014             else:
5015                 proutn(_("Rescue party"))
5016             prout(_(" boards Galileo and swoops toward planet surface."))
5017             game.iscraft = "offship"
5018             skip(1)
5019             if consumeTime():
5020                 return
5021             game.iplnet.known="shuttle_down"
5022             prout(_("Trip complete."))
5023             return
5024         else:
5025             # Ready to go back to ship
5026             prout(_("You and your mining party board the"))
5027             prout(_("shuttle craft for the trip back to the Enterprise."))
5028             skip(1)
5029             prouts(_("The short hop begins . . ."))
5030             skip(1)
5031             game.iplnet.known="known"
5032             game.icraft = True
5033             skip(1)
5034             game.landed = False
5035             if consumeTime():
5036                 return
5037             game.iscraft = "onship"
5038             game.icraft = False
5039             if game.imine:
5040                 game.icrystl = True
5041                 game.cryprob = 0.05
5042             game.imine = False
5043             prout(_("Trip complete."))
5044             return
5045     else:
5046         # Kirk on ship and so is Galileo
5047         prout(_("Mining party assembles in the hangar deck,"))
5048         prout(_("ready to board the shuttle craft \"Galileo\"."))
5049         skip(1)
5050         prouts(_("The hangar doors open; the trip begins."))
5051         skip(1)
5052         game.icraft = True
5053         game.iscraft = "offship"
5054         if consumeTime():
5055             return
5056         game.iplnet.known = "shuttle_down"
5057         game.landed = True
5058         game.icraft = False
5059         prout(_("Trip complete."))
5060         return
5061
5062 def deathray():
5063     "Use the big zapper."
5064     game.ididit = False
5065     skip(1)
5066     scanner.chew()
5067     if game.ship != 'E':
5068         prout(_("Ye Faerie Queene has no death ray."))
5069         return
5070     if len(game.enemies)==0:
5071         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
5072         return
5073     if damaged(DDRAY):
5074         prout(_("Death Ray is damaged."))
5075         return
5076     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
5077     prout(_("  is highly unpredictible.  Considering the alternatives,"))
5078     proutn(_("  are you sure this is wise?\" "))
5079     if not ja():
5080         return
5081     prout(_("Spock-  \"Acknowledged.\""))
5082     skip(1)
5083     game.ididit = True
5084     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
5085     skip(1)
5086     prout(_("Crew scrambles in emergency preparation."))
5087     prout(_("Spock and Scotty ready the death ray and"))
5088     prout(_("prepare to channel all ship's power to the device."))
5089     skip(1)
5090     prout(_("Spock-  \"Preparations complete, sir.\""))
5091     prout(_("Kirk-  \"Engage!\""))
5092     skip(1)
5093     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5094     skip(1)
5095     dprob = 0.30
5096     if game.options & OPTION_PLAIN:
5097         dprob = 0.5
5098     r = rnd.real()
5099     if r > dprob:
5100         prouts(_("Sulu- \"Captain!  It's working!\""))
5101         skip(2)
5102         while len(game.enemies) > 0:
5103             deadkl(game.enemies[-1].location, game.quad[game.enemies[-1].location.i][game.enemies[-1].location.j],game.enemies[-1].location)
5104         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5105         if game.unwon() == 0:
5106             finish(FWON)
5107         if (game.options & OPTION_PLAIN) == 0:
5108             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5109             if rnd.withprob(0.05):
5110                 prout(_("   is still operational.\""))
5111             else:
5112                 prout(_("   has been rendered nonfunctional.\""))
5113                 game.damage[DDRAY] = 39.95
5114         return
5115     r = rnd.real()        # Pick failure method
5116     if r <= 0.30:
5117         prouts(_("Sulu- \"Captain!  It's working!\""))
5118         skip(1)
5119         prouts(_("***RED ALERT!  RED ALERT!"))
5120         skip(1)
5121         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5122         skip(1)
5123         prouts(_("***RED ALERT!  RED A*L********************************"))
5124         skip(1)
5125         stars()
5126         prouts(_("******************   KA-BOOM!!!!   *******************"))
5127         skip(1)
5128         kaboom()
5129         return
5130     if r <= 0.55:
5131         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5132         skip(1)
5133         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5134         skip(1)
5135         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5136         prout(_("  have apparently been transformed into strange mutations."))
5137         prout(_("  Vulcans do not seem to be affected."))
5138         skip(1)
5139         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5140         finish(FDRAY)
5141         return
5142     if r <= 0.75:
5143         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5144         skip(2)
5145         proutn(_("Spock-  \"I believe the word is"))
5146         prouts(_(" *ASTONISHING*"))
5147         prout(_(" Mr. Sulu."))
5148         for i in range(QUADSIZE):
5149             for j in range(QUADSIZE):
5150                 if game.quad[i][j] == '.':
5151                     game.quad[i][j] = '?'
5152         prout(_("  Captain, our quadrant is now infested with"))
5153         prouts(_(" - - - - - -  *THINGS*."))
5154         skip(1)
5155         prout(_("  I have no logical explanation.\""))
5156         return
5157     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5158     skip(1)
5159     prout(_("Scotty-  \"There are so many tribbles down here"))
5160     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5161     finish(FTRIBBLE)
5162     return
5163
5164 # Code from reports.c begins here
5165
5166 def attackreport(curt):
5167     "eport status of bases under attack."
5168     if not curt:
5169         if is_scheduled(FCDBAS):
5170             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5171             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5172         elif game.isatb == 1:
5173             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5174             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5175         else:
5176             prout(_("No Starbase is currently under attack."))
5177     else:
5178         if is_scheduled(FCDBAS):
5179             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5180         if game.isatb:
5181             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5182         clreol()
5183
5184 def report():
5185     # report on general game status
5186     scanner.chew()
5187     # pylint: disable=consider-using-ternary
5188     s1 = (game.thawed and _("thawed ")) or ""
5189     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5190     s3 = (None, _("novice"), _("fair"),
5191           _("good"), _("expert"), _("emeritus"))[game.skill]
5192     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5193     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5194         prout(_("No plaque is allowed."))
5195     if game.tourn:
5196         prout(_("This is tournament game %d.") % game.tourn)
5197     prout(_("Your secret password is \"%s\"") % game.passwd)
5198     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5199                                                       (game.inkling + game.incom + game.inscom)))
5200     if game.incom - len(game.state.kcmdr):
5201         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5202     elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
5203         prout(_(", but no Commanders."))
5204     else:
5205         prout(".")
5206     if game.skill > SKILL_FAIR:
5207         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5208     if len(game.state.baseq) != game.inbase:
5209         proutn(_("There "))
5210         if game.inbase-len(game.state.baseq)==1:
5211             proutn(_("has been 1 base"))
5212         else:
5213             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5214         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5215     else:
5216         prout(_("There are %d bases.") % game.inbase)
5217     if communicating() or game.iseenit:
5218         # Don't report this if not seen and
5219         # either the radio is dead or not at base!
5220         attackreport(False)
5221         game.iseenit = True
5222     if game.casual:
5223         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5224     if game.brigcapacity != game.brigfree:
5225         embriggened = game.brigcapacity-game.brigfree
5226         if embriggened == 1:
5227             prout(_("1 Klingon in brig"))
5228         else:
5229             prout(_("%d Klingons in brig.") %  embriggened)
5230         if game.kcaptured == 0:
5231             pass
5232         elif game.kcaptured == 1:
5233             prout(_("1 captured Klingon turned in to Starfleet."))
5234         else:
5235             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5236     if game.nhelp:
5237         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5238     if game.ship == 'E':
5239         proutn(_("You have "))
5240         if game.nprobes:
5241             proutn("%d" % (game.nprobes))
5242         else:
5243             proutn(_("no"))
5244         proutn(_(" deep space probe"))
5245         if game.nprobes!=1:
5246             proutn(_("s"))
5247         prout(".")
5248     if communicating() and is_scheduled(FDSPROB):
5249         if game.isarmed:
5250             proutn(_("An armed deep space probe is in "))
5251         else:
5252             proutn(_("A deep space probe is in "))
5253         prout("Quadrant %s." % game.probe.quadrant())
5254     if game.icrystl:
5255         if game.cryprob <= .05:
5256             prout(_("Dilithium crystals aboard ship... not yet used."))
5257         else:
5258             i=0
5259             ai = 0.05
5260             while game.cryprob > ai:
5261                 ai *= 2.0
5262                 i += 1
5263             prout(_("Dilithium crystals have been used %d time%s.") % \
5264                   (i, (_("s"), "")[i==1]))
5265     skip(1)
5266
5267 def lrscan(silent):
5268     "Long-range sensor scan."
5269     if damaged(DLRSENS):
5270         # Now allow base's sensors if docked
5271         if game.condition != "docked":
5272             if not silent:
5273                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5274             return
5275         if not silent:
5276             prout(_("Starbase's long-range scan"))
5277     elif not silent:
5278         prout(_("Long-range scan"))
5279     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5280         if not silent:
5281             proutn(" ")
5282         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5283             if not Coord(x, y).valid_quadrant():
5284                 if not silent:
5285                     proutn("  -1")
5286             else:
5287                 if not damaged(DRADIO):
5288                     game.state.galaxy[x][y].charted = True
5289                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5290                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5291                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5292                 if not silent and game.state.galaxy[x][y].supernova:
5293                     proutn(" ***")
5294                 elif not silent:
5295                     cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
5296                     proutn(((3 - len(cn)) * '.') + cn)
5297         if not silent:
5298             prout(" ")
5299
5300 def damagereport():
5301     "Damage report."
5302     jdam = False
5303     scanner.chew()
5304     for i in range(NDEVICES):
5305         if damaged(i):
5306             if not jdam:
5307                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5308                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5309                 jdam = True
5310             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5311                                                game.damage[i]+0.05,
5312                                                DOCKFAC*game.damage[i]+0.005))
5313     if not jdam:
5314         prout(_("All devices functional."))
5315
5316 def rechart():
5317     "Update the chart in the Enterprise's computer from galaxy data."
5318     game.lastchart = game.state.date
5319     for i in range(GALSIZE):
5320         for j in range(GALSIZE):
5321             if game.state.galaxy[i][j].charted:
5322                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5323                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5324                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5325
5326 def chart():
5327     "Display the star chart."
5328     scanner.chew()
5329     if (game.options & OPTION_AUTOSCAN):
5330         lrscan(silent=True)
5331     if communicating():
5332         rechart()
5333     if game.lastchart < game.state.date and game.condition == "docked":
5334         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5335         rechart()
5336     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5337     if game.state.date > game.lastchart:
5338         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5339     prout("      1    2    3    4    5    6    7    8")
5340     for i in range(GALSIZE):
5341         if (game.options & OPTION_ALPHAMERIC):
5342             proutn("%c |" % letterize(i+1))
5343         else:
5344             proutn("%d |" % (i+1))
5345         for j in range(GALSIZE):
5346             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5347                 proutn("<")
5348             else:
5349                 proutn(" ")
5350             if game.state.galaxy[i][j].supernova:
5351                 show = "***"
5352             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5353                 show = ".1."
5354             elif game.state.galaxy[i][j].charted:
5355                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5356                 if (game.options & OPTION_DOTFILL):
5357                     show = show.replace(" ", ".")
5358             else:
5359                 show = "..."
5360             proutn(show)
5361             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5362                 proutn(">")
5363             else:
5364                 proutn(" ")
5365         proutn("  |")
5366         if i<GALSIZE:
5367             skip(1)
5368
5369 def sectscan(goodScan, i, j):
5370     "Light up an individual dot in a sector."
5371     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5372         if game.quad[i][j] in ('E', 'F'):
5373             if game.iscloaked:
5374                 highvideo()
5375             textcolor({"green":GREEN,
5376                        "yellow":YELLOW,
5377                        "red":RED,
5378                        "docked":CYAN,
5379                        "dead":BROWN}[game.condition])
5380         else:
5381             textcolor({'?':LIGHTMAGENTA,
5382                        'K':LIGHTRED,
5383                        'S':LIGHTRED,
5384                        'C':LIGHTRED,
5385                        'R':LIGHTRED,
5386                        'T':LIGHTRED,
5387                        '@':LIGHTGREEN,
5388                        'P':LIGHTGREEN,
5389                       }.get(game.quad[i][j], DEFAULT))
5390         proutn("%c " % game.quad[i][j])
5391         textcolor(DEFAULT)
5392     else:
5393         proutn("- ")
5394
5395 def status(req=0):
5396     "Emit status report lines"
5397     if not req or req == 1:
5398         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5399                % (game.state.date, game.state.remtime))
5400     if not req or req == 2:
5401         if game.condition != "docked":
5402             newcnd()
5403         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5404                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5405         if game.iscloaked:
5406             prout(_(", CLOAKED"))
5407     if not req or req == 3:
5408         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5409     if not req or req == 4:
5410         if damaged(DLIFSUP):
5411             if game.condition == "docked":
5412                 s = _("DAMAGED, Base provides")
5413             else:
5414                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5415         else:
5416             s = _("ACTIVE")
5417         prstat(_("Life Support"), s)
5418     if not req or req == 5:
5419         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5420     if not req or req == 6:
5421         extra = ""
5422         if game.icrystl and (game.options & OPTION_SHOWME):
5423             extra = _(" (have crystals)")
5424         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5425     if not req or req == 7:
5426         prstat(_("Torpedoes"), "%d" % (game.torps))
5427     if not req or req == 8:
5428         if damaged(DSHIELD):
5429             s = _("DAMAGED,")
5430         elif game.shldup:
5431             s = _("UP,")
5432         else:
5433             s = _("DOWN,")
5434         data = _(" %d%% %.1f units") \
5435                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5436         prstat(_("Shields"), s+data)
5437     if not req or req == 9:
5438         prstat(_("Klingons Left"), "%d" % game.unwon())
5439     if not req or req == 10:
5440         if game.options & OPTION_WORLDS:
5441             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5442             if plnet and plnet.inhabited:
5443                 prstat(_("Major system"), plnet.name)
5444             else:
5445                 prout(_("Sector is uninhabited"))
5446     elif not req or req == 11:
5447         attackreport(not req)
5448
5449 def request():
5450     "Request specified status data, a historical relic from slow TTYs."
5451     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5452     while scanner.nexttok() == "IHEOL":
5453         proutn(_("Information desired? "))
5454     scanner.chew()
5455     if scanner.token in requests:
5456         status(requests.index(scanner.token))
5457     else:
5458         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5459         prout(("  date, condition, position, lsupport, warpfactor,"))
5460         prout(("  energy, torpedoes, shields, klingons, system, time."))
5461
5462 def srscan():
5463     "Short-range scan."
5464     goodScan=True
5465     if damaged(DSRSENS):
5466         # Allow base's sensors if docked
5467         if game.condition != "docked":
5468             prout(_("   S.R. SENSORS DAMAGED!"))
5469             goodScan=False
5470         else:
5471             prout(_("  [Using Base's sensors]"))
5472     else:
5473         prout(_("     Short-range scan"))
5474     if goodScan and communicating():
5475         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5476         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5477         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5478         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5479     prout("    1 2 3 4 5 6 7 8 9 10")
5480     if game.condition != "docked":
5481         newcnd()
5482     for i in range(QUADSIZE):
5483         if (game.options & OPTION_ALPHAMERIC):
5484             proutn("%c   " % letterize(i+1))
5485         else:
5486             proutn("%2d  " % (i+1))
5487         for j in range(QUADSIZE):
5488             sectscan(goodScan, i, j)
5489         skip(1)
5490
5491 def eta():
5492     "Use computer to get estimated time of arrival for a warp jump."
5493     w1 = Coord(); w2 = Coord()
5494     prompt = False
5495     if damaged(DCOMPTR):
5496         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5497         skip(1)
5498         return
5499     if scanner.nexttok() != "IHREAL":
5500         prompt = True
5501         scanner.chew()
5502         proutn(_("Destination quadrant and/or sector? "))
5503         if scanner.nexttok()!="IHREAL":
5504             huh()
5505             return
5506     w1.j = int(scanner.real-0.5)
5507     if scanner.nexttok() != "IHREAL":
5508         huh()
5509         return
5510     w1.i = int(scanner.real-0.5)
5511     if scanner.nexttok() == "IHREAL":
5512         w2.j = int(scanner.real-0.5)
5513         if scanner.nexttok() != "IHREAL":
5514             huh()
5515             return
5516         w2.i = int(scanner.real-0.5)
5517     else:
5518         if game.quadrant.j>w1.i:
5519             w2.i = 0
5520         else:
5521             w2.i=QUADSIZE-1
5522         if game.quadrant.i>w1.j:
5523             w2.j = 0
5524         else:
5525             w2.j=QUADSIZE-1
5526     if not w1.valid_quadrant() or not w2.valid_sector():
5527         huh()
5528         return
5529     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5530                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5531     wfl = False
5532     if prompt:
5533         prout(_("Answer \"no\" if you don't know the value:"))
5534     while True:
5535         scanner.chew()
5536         proutn(_("Time or arrival date? "))
5537         if scanner.nexttok()=="IHREAL":
5538             ttime = scanner.real
5539             if ttime > game.state.date:
5540                 ttime -= game.state.date # Actually a star date
5541             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5542             if ttime <= 1e-10 or twarp > 10:
5543                 prout(_("We'll never make it, sir."))
5544                 scanner.chew()
5545                 return
5546             twarp = max(twarp, 1.0)
5547             break
5548         scanner.chew()
5549         proutn(_("Warp factor? "))
5550         if scanner.nexttok()== "IHREAL":
5551             wfl = True
5552             twarp = scanner.real
5553             if twarp<1.0 or twarp > 10.0:
5554                 huh()
5555                 return
5556             break
5557         prout(_("Captain, certainly you can give me one of these."))
5558     while True:
5559         scanner.chew()
5560         ttime = (10.0*dist)/twarp**2
5561         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5562         if tpower >= game.energy:
5563             prout(_("Insufficient energy, sir."))
5564             if not game.shldup or tpower > game.energy*2.0:
5565                 if not wfl:
5566                     return
5567                 proutn(_("New warp factor to try? "))
5568                 if scanner.nexttok() == "IHREAL":
5569                     wfl = True
5570                     twarp = scanner.real
5571                     if twarp<1.0 or twarp > 10.0:
5572                         huh()
5573                         return
5574                     continue
5575                 else:
5576                     scanner.chew()
5577                     skip(1)
5578                     return
5579             prout(_("But if you lower your shields,"))
5580             proutn(_("remaining"))
5581             tpower /= 2
5582         else:
5583             proutn(_("Remaining"))
5584         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5585         if wfl:
5586             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5587         elif twarp==1.0:
5588             prout(_("Any warp speed is adequate."))
5589         else:
5590             prout(_("Minimum warp needed is %.2f,") % (twarp))
5591             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5592         if game.state.remtime < ttime:
5593             prout(_("Unfortunately, the Federation will be destroyed by then."))
5594         if twarp > 6.0:
5595             prout(_("You'll be taking risks at that speed, Captain"))
5596         if (game.isatb==1 and game.state.kscmdr == w1 and \
5597              scheduled(FSCDBAS)< ttime+game.state.date) or \
5598             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5599             prout(_("The starbase there will be destroyed by then."))
5600         proutn(_("New warp factor to try? "))
5601         if scanner.nexttok() == "IHREAL":
5602             wfl = True
5603             twarp = scanner.real
5604             if twarp<1.0 or twarp > 10.0:
5605                 huh()
5606                 return
5607         else:
5608             scanner.chew()
5609             skip(1)
5610             return
5611
5612 # This is new in SST2K.
5613
5614 def goptions():
5615     mode = scanner.nexttok()
5616     if mode == "IHEOL":
5617         active = []
5618         for k, v in option_names.items():
5619             if (v & game.options) and k != "ALL":
5620                 active.append(k)
5621         active.sort()
5622         prout(str(" ".join(active)))
5623     elif scanner.token in {"set", "clear"}:
5624         mode = scanner.token
5625         changemask = 0
5626         while True:
5627             scanner.nexttok()
5628             if scanner.type == "IHEOL":
5629                 break
5630             if scanner.token.upper() in option_names:
5631                 changemask |= option_names[scanner.token.upper()]
5632             else:
5633                 prout(_("No such option as ") + scanner.token)
5634         if mode == "set":
5635             if (not (game.options & OPTION_CURSES)) and (changemask & OPTION_CURSES):
5636                 iostart()
5637             game.options |= changemask
5638         elif mode == "clear":
5639             if (game.options & OPTION_CURSES) and (not (changemask & OPTION_CURSES)):
5640                 ioend()
5641             game.options &=~ changemask
5642         prout(_("Acknowledged, Captain."))
5643     else:
5644         huh()
5645     scanner.chew()
5646     skip(1)
5647
5648 # Code from setup.c begins here
5649
5650 def prelim():
5651     "Issue a historically correct banner."
5652     skip(2)
5653     prout(_("-SUPER- STAR TREK"))
5654     skip(1)
5655 # From the FORTRAN original
5656 #    prout(_("Latest update-21 Sept 78"))
5657 #    skip(1)
5658
5659 def freeze(boss):
5660     "Save game."
5661     if boss:
5662         scanner.push("emsave.trk")
5663     key = scanner.nexttok()
5664     if key == "IHEOL":
5665         proutn(_("File name: "))
5666         key = scanner.nexttok()
5667     if key != "IHALPHA":
5668         huh()
5669         return
5670     if '.' not in scanner.token:
5671         scanner.token += ".trk"
5672     try:
5673         fp = open(scanner.token, "wb")
5674     except IOError:
5675         prout(_("Can't freeze game as file %s") % scanner.token)
5676         return
5677     pickle.dump(game, fp)
5678     fp.close()
5679     scanner.chew()
5680
5681 def thaw():
5682     "Retrieve saved game."
5683     global game
5684     game.passwd = None
5685     key = scanner.nexttok()
5686     if key == "IHEOL":
5687         proutn(_("File name: "))
5688         key = scanner.nexttok()
5689     if key != "IHALPHA":
5690         huh()
5691         return True
5692     if '.' not in scanner.token:
5693         scanner.token += ".trk"
5694     try:
5695         fp = open(scanner.token, "rb")
5696     except IOError:
5697         prout(_("Can't thaw game in %s") % scanner.token)
5698         return True
5699     game = pickle.load(fp)
5700     fp.close()
5701     scanner.chew()
5702     return False
5703
5704 # I used <http://www.memory-alpha.org> to find planets
5705 # with references in ST:TOS.  Earth and the Alpha Centauri
5706 # Colony have been omitted.
5707 #
5708 # Some planets marked Class G and P here will be displayed as class M
5709 # because of the way planets are generated. This is a known bug.
5710 systnames = (
5711     # Federation Worlds
5712     _("Andoria (Fesoan)"),        # several episodes
5713     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5714     _("Vulcan (T'Khasi)"),        # many episodes
5715     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5716     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5717     _("Ardana"),                # TOS: "The Cloud Minders"
5718     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5719     _("Gideon"),                # TOS: "The Mark of Gideon"
5720     _("Aldebaran III"),                # TOS: "The Deadly Years"
5721     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5722     _("Altair IV"),                # TOS: "Amok Time
5723     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5724     _("Benecia"),                # TOS: "The Conscience of the King"
5725     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5726     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5727     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5728     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5729     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5730     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5731     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5732     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5733     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5734     _("Janus IV"),                # TOS: "The Devil in the Dark"
5735     _("Makus III"),                # TOS: "The Galileo Seven"
5736     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5737     _("Omega IV"),                # TOS: "The Omega Glory"
5738     _("Regulus V"),                # TOS: "Amok Time
5739     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5740     # Worlds from BSD Trek
5741     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5742     _("Beta III"),                # TOS: "The Return of the Archons"
5743     _("Triacus"),                # TOS: "And the Children Shall Lead",
5744     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5745     #        # Others
5746     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5747     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5748     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5749     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5750     #    _("Tiburon"),                # TOS: "The Way to Eden"
5751     #    _("Merak II"),                # TOS: "The Cloud Minders"
5752     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5753     #    _("Iotia"),                # TOS: "A Piece of the Action"
5754 )
5755
5756 device = (
5757     _("S. R. Sensors"), \
5758     _("L. R. Sensors"), \
5759     _("Phasers"), \
5760     _("Photon Tubes"), \
5761     _("Life Support"), \
5762     _("Warp Engines"), \
5763     _("Impulse Engines"), \
5764     _("Shields"), \
5765     _("Subspace Radio"), \
5766     _("Shuttle Craft"), \
5767     _("Computer"), \
5768     _("Navigation System"), \
5769     _("Transporter"), \
5770     _("Shield Control"), \
5771     _("Death Ray"), \
5772     _("D. S. Probe"), \
5773     _("Cloaking Device"), \
5774 )
5775
5776 def setup():
5777     "Prepare to play, set up cosmos."
5778     w = Coord()
5779     #  Decide how many of everything
5780     if choose():
5781         return # frozen game
5782     # Prepare the Enterprise
5783     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5784     game.ship = 'E'
5785     game.state.crew = FULLCREW
5786     game.energy = game.inenrg = 5000.0
5787     game.shield = game.inshld = 2500.0
5788     game.inlsr = 4.0
5789     game.lsupres = 4.0
5790     game.quadrant = randplace(GALSIZE)
5791     game.sector = randplace(QUADSIZE)
5792     game.torps = game.intorps = 10
5793     game.nprobes = rnd.integer(2, 5)
5794     game.warpfac = 5.0
5795     for i in range(NDEVICES):
5796         game.damage[i] = 0.0
5797     # Set up assorted game parameters
5798     game.battle = Coord()
5799     game.state.date = game.indate = 100.0 * rnd.real(20, 51)
5800     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5801     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5802     game.isatb = game.state.nplankl = 0
5803     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5804     game.iscraft = "onship"
5805     game.landed = False
5806     game.alive = True
5807
5808     # the galaxy
5809     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5810     # the starchart
5811     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5812
5813     game.state.planets = []      # Planet information
5814     game.state.baseq = []      # Base quadrant coordinates
5815     game.state.kcmdr = []      # Commander quadrant coordinates
5816     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5817
5818     # Starchart is functional but we've never seen it
5819     game.lastchart = FOREVER
5820     # Put stars in the galaxy
5821     game.instar = 0
5822     for i in range(GALSIZE):
5823         for j in range(GALSIZE):
5824             # Can't have more stars per quadrant than fit in one decimal digit,
5825             # if we do the chart representation will break.
5826             k = rnd.integer(1, min(10, QUADSIZE**2/10))
5827             game.instar += k
5828             game.state.galaxy[i][j].stars = k
5829     # Locate star bases in galaxy
5830     if game.idebug:
5831         prout("=== Allocating %d bases" % game.inbase)
5832     for i in range(game.inbase):
5833         while True:
5834             while True:
5835                 w = randplace(GALSIZE)
5836                 if not game.state.galaxy[w.i][w.j].starbase:
5837                     break
5838             contflag = False
5839             # C version: for (j = i-1; j > 0; j--)
5840             # so it did them in the opposite order.
5841             for j in range(1, i):
5842                 # Improved placement algorithm to spread out bases
5843                 distq = (w - game.state.baseq[j]).distance()
5844                 if distq < 6.0*(BASEMAX+1-game.inbase) and rnd.withprob(0.75):
5845                     contflag = True
5846                     if game.idebug:
5847                         prout("=== Abandoning base #%d at %s" % (i, w))
5848                     break
5849                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5850                     if game.idebug:
5851                         prout("=== Saving base #%d, close to #%d" % (i, j))
5852             if not contflag:
5853                 break
5854         if game.idebug:
5855             prout("=== Placing base #%d in quadrant %s" % (i, w))
5856         game.state.baseq.append(w)
5857         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5858     # Position ordinary Klingon Battle Cruisers
5859     krem = game.inkling
5860     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5861     klumper = min(klumper, MAXKLQUAD)
5862     while True:
5863         r = rnd.real()
5864         klump = int((1.0 - r*r)*klumper)
5865         klump = min(klump, krem)
5866         krem -= klump
5867         while True:
5868             w = randplace(GALSIZE)
5869             if not game.state.galaxy[w.i][w.j].supernova and \
5870                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5871                 break
5872         game.state.galaxy[w.i][w.j].klingons += klump
5873         if krem <= 0:
5874             break
5875     # Position Klingon Commander Ships
5876     for i in range(game.incom):
5877         while True:
5878             w = randplace(GALSIZE)
5879             if not welcoming(w) or w in game.state.kcmdr:
5880                 continue
5881             if (game.state.galaxy[w.i][w.j].klingons or rnd.withprob(0.25)):
5882                 break
5883         game.state.galaxy[w.i][w.j].klingons += 1
5884         game.state.kcmdr.append(w)
5885     # Locate planets in galaxy
5886     for i in range(game.inplan):
5887         while True:
5888             w = randplace(GALSIZE)
5889             if game.state.galaxy[w.i][w.j].planet is None:
5890                 break
5891         new = Planet()
5892         new.quadrant = w
5893         new.crystals = "absent"
5894         if (game.options & OPTION_WORLDS) and i < NINHAB:
5895             new.pclass = "M"        # All inhabited planets are class M
5896             new.crystals = "absent"
5897             new.known = "known"
5898             new.name = systnames[i]
5899             new.inhabited = True
5900         else:
5901             new.pclass = ("M", "N", "O")[rnd.integer(0, 3)]
5902             if rnd.withprob(0.33):
5903                 new.crystals = "present"
5904             new.known = "unknown"
5905             new.inhabited = False
5906         game.state.galaxy[w.i][w.j].planet = new
5907         game.state.planets.append(new)
5908     # Locate Romulans
5909     for i in range(game.state.nromrem):
5910         w = randplace(GALSIZE)
5911         game.state.galaxy[w.i][w.j].romulans += 1
5912     # Place the Super-Commander if needed
5913     if game.state.nscrem > 0:
5914         while True:
5915             w = randplace(GALSIZE)
5916             if welcoming(w):
5917                 break
5918         game.state.kscmdr = w
5919         game.state.galaxy[w.i][w.j].klingons += 1
5920     # Initialize times for extraneous events
5921     schedule(FSNOVA, expran(0.5 * game.intime))
5922     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5923     schedule(FSNAP, rnd.real(1.0, 2.0)) # Force an early snapshot
5924     schedule(FBATTAK, expran(0.3*game.intime))
5925     unschedule(FCDBAS)
5926     if game.state.nscrem:
5927         schedule(FSCMOVE, 0.2777)
5928     else:
5929         unschedule(FSCMOVE)
5930     unschedule(FSCDBAS)
5931     unschedule(FDSPROB)
5932     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5933         schedule(FDISTR, expran(1.0 + game.intime))
5934     else:
5935         unschedule(FDISTR)
5936     unschedule(FENSLV)
5937     unschedule(FREPRO)
5938     # Place thing (in tournament game, we don't want one!)
5939     # New in SST2K: never place the Thing near a starbase.
5940     # This makes sense and avoids a special case in the old code.
5941     global thing
5942     if game.tourn is None:
5943         while True:
5944             thing = randplace(GALSIZE)
5945             if thing not in game.state.baseq:
5946                 break
5947     skip(2)
5948     game.state.snap = False
5949     if game.skill == SKILL_NOVICE:
5950         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5951         prout(_("a deadly Klingon invasion force. As captain of the United"))
5952         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5953         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5954         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5955         prout(_("your mission.  As you proceed you may be given more time."))
5956         skip(1)
5957         prout(_("You will have %d supporting starbases.") % (game.inbase))
5958         proutn(_("Starbase locations-  "))
5959     else:
5960         prout(_("Stardate %d.") % int(game.state.date))
5961         skip(1)
5962         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5963         prout(_("An unknown number of Romulans."))
5964         if game.state.nscrem:
5965             prout(_("And one (GULP) Super-Commander."))
5966         prout(_("%d stardates.") % int(game.intime))
5967         proutn(_("%d starbases in ") % game.inbase)
5968     for i in range(game.inbase):
5969         proutn(repr(game.state.baseq[i]))
5970         proutn("  ")
5971     skip(2)
5972     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5973     proutn(_(" Sector %s") % game.sector)
5974     skip(2)
5975     prout(_("Good Luck!"))
5976     if game.state.nscrem:
5977         prout(_("  YOU'LL NEED IT."))
5978     waitfor()
5979     clrscr()
5980     setwnd(message_window)
5981     newqad()
5982     if len(game.enemies) - (thing == game.quadrant) - (game.tholian is not None):
5983         game.shldup = True
5984     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5985         attack(torps_ok=False)
5986
5987 def choose():
5988     "Choose your game type."
5989     while True:
5990         game.tourn = game.length = 0
5991         game.thawed = False
5992         game.skill = SKILL_NONE
5993         # Do not chew here, we want to use command-line tokens
5994         if not scanner.inqueue: # Can start with command line options
5995             proutn(_("Would you like a regular, tournament, or saved game? "))
5996         scanner.nexttok()
5997         if scanner.sees("tournament"):
5998             while scanner.nexttok() == "IHEOL":
5999                 proutn(_("Type in tournament number-"))
6000             if scanner.real == 0:
6001                 scanner.chew()
6002                 continue # We don't want a blank entry
6003             game.tourn = int(round(scanner.real))
6004             rnd.seed(scanner.real)
6005             if logfp:
6006                 logfp.write("# rnd.seed(%d)\n" % scanner.real)
6007             break
6008         if scanner.sees("saved") or scanner.sees("frozen"):
6009             if thaw():
6010                 continue
6011             scanner.chew()
6012             if game.passwd is None:
6013                 continue
6014             if not game.alldone:
6015                 game.thawed = True # No plaque if not finished
6016             report()
6017             waitfor()
6018             return True
6019         if scanner.sees("regular"):
6020             break
6021         proutn(_("What is \"%s\"? ") % scanner.token)
6022         scanner.chew()
6023     while game.length==0 or game.skill==SKILL_NONE:
6024         if scanner.nexttok() == "IHALPHA":
6025             if scanner.sees("short"):
6026                 game.length = 1
6027             elif scanner.sees("medium"):
6028                 game.length = 2
6029             elif scanner.sees("long"):
6030                 game.length = 4
6031             elif scanner.sees("novice"):
6032                 game.skill = SKILL_NOVICE
6033             elif scanner.sees("fair"):
6034                 game.skill = SKILL_FAIR
6035             elif scanner.sees("good"):
6036                 game.skill = SKILL_GOOD
6037             elif scanner.sees("expert"):
6038                 game.skill = SKILL_EXPERT
6039             elif scanner.sees("emeritus"):
6040                 game.skill = SKILL_EMERITUS
6041             else:
6042                 proutn(_("What is \""))
6043                 proutn(scanner.token)
6044                 prout("\"?")
6045         else:
6046             scanner.chew()
6047             if game.length==0:
6048                 proutn(_("Would you like a Short, Medium, or Long game? "))
6049             elif game.skill == SKILL_NONE:
6050                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
6051     # Choose game options -- added by ESR for SST2K
6052     if scanner.nexttok() != "IHALPHA":
6053         scanner.chew()
6054         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
6055         scanner.nexttok()
6056     if scanner.sees("plain"):
6057         # Approximates the UT FORTRAN version.
6058         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)
6059         game.options |= OPTION_PLAIN
6060     elif scanner.sees("almy"):
6061         # Approximates Tom Almy's version.
6062         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR | OPTION_DOTFILL | OPTION_ALPHAMERIC)
6063         game.options |= OPTION_ALMY
6064     elif scanner.sees("fancy") or scanner.sees("\n"):
6065         pass
6066     elif len(scanner.token):
6067         proutn(_("What is \"%s\"?") % scanner.token)
6068     setpassword()
6069     if game.passwd == "debug":
6070         game.idebug = True
6071         prout("=== Debug mode enabled.")
6072     # Use parameters to generate initial values of things
6073     game.damfac = 0.5 * game.skill
6074     game.inbase = rnd.integer(BASEMIN, BASEMAX+1)
6075     game.inplan = 0
6076     if game.options & OPTION_PLANETS:
6077         game.inplan += rnd.integer(MAXUNINHAB/2, MAXUNINHAB+1)
6078     if game.options & OPTION_WORLDS:
6079         game.inplan += int(NINHAB)
6080     game.state.nromrem = game.inrom = rnd.integer(2 * game.skill)
6081     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
6082     game.state.remtime = 7.0 * game.length
6083     game.intime = game.state.remtime
6084     game.inkling = int(2.0*game.intime*((game.skill+1 - 2*rnd.real())*game.skill*0.1+.15))
6085     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*rnd.real()))
6086     game.state.remres = (game.inkling+4*game.incom)*game.intime
6087     game.inresor = game.state.remres
6088     if game.inkling > 50:
6089         game.inbase += 1
6090     return False
6091
6092 def dropin(iquad=None):
6093     "Drop a feature on a random dot in the current quadrant."
6094     while True:
6095         w = randplace(QUADSIZE)
6096         if game.quad[w.i][w.j] == '.':
6097             break
6098     if iquad is not None:
6099         game.quad[w.i][w.j] = iquad
6100     return w
6101
6102 def newcnd():
6103     "Update our alert status."
6104     game.condition = "green"
6105     if game.energy < 1000.0:
6106         game.condition = "yellow"
6107     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
6108         game.condition = "red"
6109     if not game.alive:
6110         game.condition="dead"
6111
6112 def newkling():
6113     "Drop new Klingon into current quadrant."
6114     return Enemy('K', loc=dropin(), power=rnd.real(300,450)+25.0*game.skill)
6115
6116 def sortenemies():
6117     "Sort enemies by distance so 'nearest' is meaningful."
6118     game.enemies.sort(key=lambda x: x.kdist)
6119
6120 def newqad():
6121     "Set up a new state of quadrant, for when we enter or re-enter it."
6122     game.justin = True
6123     game.iplnet = None
6124     game.neutz = game.inorbit = game.landed = False
6125     game.ientesc = game.iseenit = game.isviolreported = False
6126     game.tholian = None
6127     # Create a blank quadrant
6128     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6129     if game.iscate:
6130         # Attempt to escape Super-commander, so tbeam back!
6131         game.iscate = False
6132         game.ientesc = True
6133     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6134     # cope with supernova
6135     if q.supernova:
6136         return
6137     game.klhere = q.klingons
6138     game.irhere = q.romulans
6139     # Position Starship
6140     game.quad[game.sector.i][game.sector.j] = game.ship
6141     game.enemies = []
6142     if q.klingons:
6143         # Position ordinary Klingons
6144         for _i in range(game.klhere):
6145             newkling()
6146         # If we need a commander, promote a Klingon
6147         for cmdr in game.state.kcmdr:
6148             if cmdr == game.quadrant:
6149                 e = game.enemies[game.klhere-1]
6150                 game.quad[e.location.i][e.location.j] = 'C'
6151                 e.power = rnd.real(950,1350) + 50.0*game.skill
6152                 break
6153         # If we need a super-commander, promote a Klingon
6154         if game.quadrant == game.state.kscmdr:
6155             e = game.enemies[0]
6156             game.quad[e.location.i][e.location.j] = 'S'
6157             e.power = rnd.real(1175.0,  1575.0) + 125.0*game.skill
6158             game.iscate = (game.remkl() > 1)
6159     # Put in Romulans if needed
6160     for _i in range(q.romulans):
6161         Enemy('R', loc=dropin(), power=rnd.real(400.0,850.0)+50.0*game.skill)
6162     # If quadrant needs a starbase, put it in
6163     if q.starbase:
6164         game.base = dropin('B')
6165     # If quadrant needs a planet, put it in
6166     if q.planet:
6167         game.iplnet = q.planet
6168         if not q.planet.inhabited:
6169             game.plnet = dropin('P')
6170         else:
6171             game.plnet = dropin('@')
6172     # Check for condition
6173     newcnd()
6174     # Check for RNZ
6175     if game.irhere > 0 and game.klhere == 0:
6176         game.neutz = True
6177         if not damaged(DRADIO):
6178             skip(1)
6179             prout(_("LT. Uhura- \"Captain, an urgent message."))
6180             prout(_("  I'll put it on audio.\"  CLICK"))
6181             skip(1)
6182             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6183             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6184     # Put in THING if needed
6185     if thing == game.quadrant:
6186         Enemy(etype='?', loc=dropin(),
6187               power=rnd.real(6000,6500.0)+250.0*game.skill)
6188         if not damaged(DSRSENS):
6189             skip(1)
6190             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6191             prout(_("    Please examine your short-range scan.\""))
6192     # Decide if quadrant needs a Tholian; lighten up if skill is low
6193     if game.options & OPTION_THOLIAN:
6194         if (game.skill < SKILL_GOOD and rnd.withprob(0.02)) or \
6195             (game.skill == SKILL_GOOD and rnd.withprob(0.05)) or \
6196             (game.skill > SKILL_GOOD and rnd.withprob(0.08)):
6197             w = Coord()
6198             while True:
6199                 w.i = rnd.withprob(0.5) * (QUADSIZE-1)
6200                 w.j = rnd.withprob(0.5) * (QUADSIZE-1)
6201                 if game.quad[w.i][w.j] == '.':
6202                     break
6203             game.tholian = Enemy(etype='T', loc=w,
6204                                  power=rnd.integer(100, 500) + 25.0*game.skill)
6205             # Reserve unoccupied corners
6206             if game.quad[0][0]=='.':
6207                 game.quad[0][0] = 'X'
6208             if game.quad[0][QUADSIZE-1]=='.':
6209                 game.quad[0][QUADSIZE-1] = 'X'
6210             if game.quad[QUADSIZE-1][0]=='.':
6211                 game.quad[QUADSIZE-1][0] = 'X'
6212             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6213                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6214     sortenemies()
6215     # And finally the stars
6216     for _i in range(q.stars):
6217         dropin('*')
6218     # Put in a few black holes
6219     for _i in range(1, 3+1):
6220         if rnd.withprob(0.5):
6221             dropin(' ')
6222     # Take out X's in corners if Tholian present
6223     if game.tholian:
6224         if game.quad[0][0]=='X':
6225             game.quad[0][0] = '.'
6226         if game.quad[0][QUADSIZE-1]=='X':
6227             game.quad[0][QUADSIZE-1] = '.'
6228         if game.quad[QUADSIZE-1][0]=='X':
6229             game.quad[QUADSIZE-1][0] = '.'
6230         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6231             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6232     # This should guarantee that replay games don't lose info about the chart
6233     if (game.options & OPTION_AUTOSCAN) or replayfp:
6234         lrscan(silent=True)
6235
6236 def setpassword():
6237     "Set the self-destruct password."
6238     if game.options & OPTION_PLAIN:
6239         while True:
6240             scanner.chew()
6241             proutn(_("Please type in a secret password- "))
6242             scanner.nexttok()
6243             game.passwd = scanner.token
6244             if game.passwd is not None:
6245                 break
6246     else:
6247         game.passwd = ""
6248         game.passwd += chr(ord('a')+rnd.integer(26))
6249         game.passwd += chr(ord('a')+rnd.integer(26))
6250         game.passwd += chr(ord('a')+rnd.integer(26))
6251
6252 # Code from sst.c begins here
6253
6254 commands = [
6255     ("SRSCAN",           OPTION_TTY),
6256     ("STATUS",           OPTION_TTY),
6257     ("REQUEST",          OPTION_TTY),
6258     ("LRSCAN",           OPTION_TTY),
6259     ("PHASERS",          0),
6260     ("TORPEDO",          0),
6261     ("PHOTONS",          0),
6262     ("MOVE",             0),
6263     ("SHIELDS",          0),
6264     ("DOCK",             0),
6265     ("DAMAGES",          0),
6266     ("CHART",            0),
6267     ("IMPULSE",          0),
6268     ("REST",             0),
6269     ("WARP",             0),
6270     ("SENSORS",          OPTION_PLANETS),
6271     ("ORBIT",            OPTION_PLANETS),
6272     ("TRANSPORT",        OPTION_PLANETS),
6273     ("MINE",             OPTION_PLANETS),
6274     ("CRYSTALS",         OPTION_PLANETS),
6275     ("SHUTTLE",          OPTION_PLANETS),
6276     ("PLANETS",          OPTION_PLANETS),
6277     ("REPORT",           0),
6278     ("COMPUTER",         0),
6279     ("COMMANDS",         0),
6280     ("EMEXIT",           0),
6281     ("PROBE",            OPTION_PROBE),
6282     ("SAVE",             0),
6283     ("FREEZE",           0),        # Synonym for SAVE
6284     ("OPTIONS",          0),
6285     ("ABANDON",          0),
6286     # No abbreviations accepted after this point
6287     ("DESTRUCT",         0),
6288     ("DEATHRAY",         0),
6289     ("CAPTURE",          OPTION_CAPTURE),
6290     ("CLOAK",            OPTION_CLOAK),
6291     ("DEBUG",            0),
6292     ("MAYDAY",           0),
6293     ("SOS",              0),        # Synonym for MAYDAY
6294     ("CALL",             0),        # Synonym for MAYDAY
6295     ("QUIT",             0),
6296     ("HELP",             0),
6297     ("SCORE",            0),
6298     ("CURSES",           0),
6299     ("",                 0),
6300 ]
6301
6302 def listCommands():
6303     "Generate a list of legal commands."
6304     prout(_("LEGAL COMMANDS ARE:"))
6305     emitted = 0
6306     for (key, opt) in commands:
6307         if not opt or (opt & game.options):
6308             proutn("%-12s " % key)
6309             emitted += 1
6310             if emitted % 5 == 4:
6311                 skip(1)
6312     skip(1)
6313
6314 def helpme():
6315     "Browse on-line help."
6316     key = scanner.nexttok()
6317     while True:
6318         if key == "IHEOL":
6319             setwnd(prompt_window)
6320             proutn(_("Help on what command? "))
6321             key = scanner.nexttok()
6322         setwnd(message_window)
6323         if key == "IHEOL":
6324             return
6325         cmds = [x[0] for x in commands]
6326         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6327             break
6328         skip(1)
6329         listCommands()
6330         key = "IHEOL"
6331         scanner.chew()
6332         skip(1)
6333     cmd = scanner.token.upper()
6334     for directory in docpath:
6335         try:
6336             fp = open(os.path.join(directory, "sst.doc"), "r")
6337             break
6338         except IOError:
6339             pass
6340     else:
6341         prout(_("Spock-  \"Captain, that information is missing from the"))
6342         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6343         proutn(_("   in these directories: %s") % ":".join(docpath))
6344         prout(".\"")
6345         # This used to continue: "You need to find SST.DOC and put
6346         # it in the current directory."
6347         return
6348     while True:
6349         linebuf = fp.readline()
6350         if linebuf == '':
6351             prout(_("Spock- \"Captain, there is no information on that command.\""))
6352             fp.close()
6353             return
6354         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6355             linebuf = linebuf[3:].strip()
6356             if cmd.upper() == linebuf:
6357                 break
6358     skip(1)
6359     prout(_("Spock- \"Captain, I've found the following information:\""))
6360     skip(1)
6361     while True:
6362         linebuf = fp.readline()
6363         if "******" in linebuf:
6364             break
6365         proutn(linebuf)
6366     fp.close()
6367
6368 def makemoves():
6369     "Command-interpretation loop."
6370     def checkviol():
6371         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6372             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6373             game.ncviol += 1
6374             game.isviolreported = True
6375     while True:         # command loop
6376         drawmaps(1)
6377         while True:        # get a command
6378             hitme = False
6379             game.optime = game.justin = False
6380             scanner.chew()
6381             setwnd(prompt_window)
6382             clrscr()
6383             proutn("COMMAND> ")
6384             if scanner.nexttok() == "IHEOL":
6385                 if game.options & OPTION_CURSES:
6386                     makechart()
6387                 continue
6388             elif scanner.token == "":
6389                 continue
6390             game.ididit = False
6391             clrscr()
6392             setwnd(message_window)
6393             clrscr()
6394             abandon_passed = False
6395             cmd = ""    # Force cmd to persist after loop
6396             opt = 0     # Force opt to persist after loop
6397             for (cmd, opt) in commands:
6398                 # commands after ABANDON cannot be abbreviated
6399                 if cmd == "ABANDON":
6400                     abandon_passed = True
6401                 if cmd == scanner.token.upper() or (not abandon_passed \
6402                         and cmd.startswith(scanner.token.upper())):
6403                     break
6404             if cmd == "":
6405                 listCommands()
6406                 continue
6407             elif opt and not (opt & game.options):
6408                 huh()
6409             else:
6410                 break
6411         if game.options & OPTION_CURSES:
6412             prout("COMMAND> %s" % cmd)
6413         if cmd == "SRSCAN":                # srscan
6414             srscan()
6415         elif cmd == "STATUS":                # status
6416             status()
6417         elif cmd == "REQUEST":                # status request
6418             request()
6419         elif cmd == "LRSCAN":                # long range scan
6420             lrscan(silent=False)
6421         elif cmd == "PHASERS":                # phasers
6422             phasers()
6423             if game.ididit:
6424                 checkviol()
6425                 hitme = True
6426         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6427             torps()
6428             if game.ididit:
6429                 checkviol()
6430                 hitme = True
6431         elif cmd == "MOVE":                # move under warp
6432             warp(wcourse=None, involuntary=False)
6433         elif cmd == "SHIELDS":                # shields
6434             doshield(shraise=False)
6435             if game.ididit:
6436                 hitme = True
6437                 game.shldchg = False
6438         elif cmd == "DOCK":                # dock at starbase
6439             dock(True)
6440             if game.ididit:
6441                 attack(torps_ok=False)
6442         elif cmd == "DAMAGES":                # damage reports
6443             damagereport()
6444         elif cmd == "CHART":                # chart
6445             makechart()
6446         elif cmd == "IMPULSE":                # impulse
6447             impulse()
6448         elif cmd == "REST":                # rest
6449             wait()
6450             if game.ididit:
6451                 hitme = True
6452         elif cmd == "WARP":                # warp
6453             setwarp()
6454         elif cmd == "SENSORS":                # sensors
6455             sensor()
6456         elif cmd == "ORBIT":                # orbit
6457             orbit()
6458             if game.ididit:
6459                 hitme = True
6460         elif cmd == "TRANSPORT":                # transport "beam"
6461             beam()
6462         elif cmd == "MINE":                # mine
6463             mine()
6464             if game.ididit:
6465                 hitme = True
6466         elif cmd == "CRYSTALS":                # crystals
6467             usecrystals()
6468             if game.ididit:
6469                 hitme = True
6470         elif cmd == "SHUTTLE":                # shuttle
6471             shuttle()
6472             if game.ididit:
6473                 hitme = True
6474         elif cmd == "PLANETS":                # Planet list
6475             survey()
6476         elif cmd == "REPORT":                # Game Report
6477             report()
6478         elif cmd == "COMPUTER":                # use COMPUTER!
6479             eta()
6480         elif cmd == "COMMANDS":
6481             listCommands()
6482         elif cmd == "EMEXIT":                # Emergency exit
6483             clrscr()                        # Hide screen
6484             freeze(True)                # forced save
6485             raise SystemExit(1)                # And quick exit
6486         elif cmd == "PROBE":
6487             probe()                        # Launch probe
6488             if game.ididit:
6489                 hitme = True
6490         elif cmd == "ABANDON":                # Abandon Ship
6491             abandon()
6492         elif cmd == "DESTRUCT":                # Self Destruct
6493             selfdestruct()
6494         elif cmd == "SAVE":                # Save Game
6495             freeze(False)
6496             clrscr()
6497             if game.skill > SKILL_GOOD:
6498                 prout(_("WARNING--Saved games produce no plaques!"))
6499         elif cmd == "DEATHRAY":                # Try a desparation measure
6500             deathray()
6501             if game.ididit:
6502                 hitme = True
6503         elif cmd == "CAPTURE":
6504             capture()
6505         elif cmd == "CLOAK":
6506             cloak()
6507         elif cmd == "DEBUGCMD":                # What do we want for debug???
6508             debugme()
6509         elif cmd == "MAYDAY":                # Call for help
6510             mayday()
6511             if game.ididit:
6512                 hitme = True
6513         elif cmd == "QUIT":
6514             game.alldone = True                # quit the game
6515         elif cmd == "HELP":
6516             helpme()                        # get help
6517         elif cmd == "SCORE":
6518             score()                         # see current score
6519         elif cmd == "CURSES":
6520             game.options |= (OPTION_CURSES | OPTION_COLOR)
6521             iostart()
6522         elif cmd == "OPTIONS":
6523             goptions()
6524         while True:
6525             if game.alldone:
6526                 break                # Game has ended
6527             if game.optime != 0.0:
6528                 events()
6529                 if game.alldone:
6530                     break        # Events did us in
6531             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6532                 atover(False)
6533                 continue
6534             if hitme and not game.justin:
6535                 attack(torps_ok=True)
6536                 if game.alldone:
6537                     break
6538                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6539                     atover(False)
6540                     hitme = True
6541                     continue
6542             break
6543         if game.alldone:
6544             break
6545     if game.idebug:
6546         prout("=== Ending")
6547
6548 def cramen(ch):
6549     "Emit the name of an enemy or feature."
6550     if   ch == 'R': s = _("Romulan")
6551     elif ch == 'K': s = _("Klingon")
6552     elif ch == 'C': s = _("Commander")
6553     elif ch == 'S': s = _("Super-commander")
6554     elif ch == '*': s = _("Star")
6555     elif ch == 'P': s = _("Planet")
6556     elif ch == 'B': s = _("Starbase")
6557     elif ch == ' ': s = _("Black hole")
6558     elif ch == 'T': s = _("Tholian")
6559     elif ch == '#': s = _("Tholian web")
6560     elif ch == '?': s = _("Stranger")
6561     elif ch == '@': s = _("Inhabited World")
6562     else: s = "Unknown??"
6563     return s
6564
6565 def crmena(loud, enemy, loctype, w):
6566     "Emit the name of an enemy and his location."
6567     buf = ""
6568     if loud:
6569         buf += "***"
6570     buf += cramen(enemy) + _(" at ")
6571     if loctype == "quadrant":
6572         buf += _("Quadrant ")
6573     elif loctype == "sector":
6574         buf += _("Sector ")
6575     return buf + repr(w)
6576
6577 def crmshp():
6578     "Emit our ship name."
6579     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6580
6581 def stars():
6582     "Emit a line of stars"
6583     prouts("******************************************************")
6584     skip(1)
6585
6586 def expran(avrage):
6587     return -avrage*math.log(1e-7 + rnd.real())
6588
6589 def randplace(size):
6590     "Choose a random location."
6591     w = Coord()
6592     w.i = rnd.integer(size)
6593     w.j = rnd.integer(size)
6594     return w
6595
6596 class sstscanner:
6597     def __init__(self):
6598         self.type = None
6599         self.token = None
6600         self.real = 0.0
6601         self.inqueue = []
6602     def nexttok(self):
6603         # Get a token from the user
6604         self.real = 0.0
6605         self.token = ''
6606         # Fill the token queue if nothing here
6607         while not self.inqueue:
6608             sline = cgetline()
6609             if curwnd==prompt_window:
6610                 clrscr()
6611                 setwnd(message_window)
6612                 clrscr()
6613             if sline == '':
6614                 return None
6615             if not sline:
6616                 continue
6617             else:
6618                 self.inqueue = sline.lstrip().split() + ["\n"]
6619         # From here on in it's all looking at the queue
6620         self.token = self.inqueue.pop(0)
6621         if self.token == "\n":
6622             self.type = "IHEOL"
6623             return "IHEOL"
6624         try:
6625             self.real = float(self.token)
6626             self.type = "IHREAL"
6627             return "IHREAL"
6628         except ValueError:
6629             pass
6630         # Treat as alpha
6631         self.token = self.token.lower()
6632         self.type = "IHALPHA"
6633         self.real = None
6634         return "IHALPHA"
6635     def append(self, tok):
6636         self.inqueue.append(tok)
6637     def push(self, tok):
6638         self.inqueue.insert(0, tok)
6639     def waiting(self):
6640         return self.inqueue
6641     def chew(self):
6642         # Demand input for next scan
6643         self.inqueue = []
6644         self.real = self.token = None
6645     def sees(self, s):
6646         # compares s to item and returns true if it matches to the length of s
6647         return s.startswith(self.token)
6648     def int(self):
6649         # Round token value to nearest integer
6650         return int(round(self.real))
6651     def getcoord(self):
6652         s = Coord()
6653         self.nexttok()
6654         if (game.options & OPTION_ALPHAMERIC):
6655             if (self.type == "IHALPHA") and (self.token[0] in "abcdefghij") and (self.token[1] in "0123456789"):
6656                 s.i = ord(self.token[0]) - ord("a")
6657                 try:
6658                     s.j = int(self.token[-1:])-1
6659                 except TypeError:
6660                     huh()
6661                     return None
6662                 return s
6663         if self.type != "IHREAL":
6664             huh()
6665             return None
6666         s.i = self.int()-1
6667         self.nexttok()
6668         if self.type != "IHREAL":
6669             huh()
6670             return None
6671         s.j = self.int()-1
6672         return s
6673     def __repr__(self):
6674         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6675
6676 def ja():
6677     "Yes-or-no confirmation."
6678     scanner.chew()
6679     while True:
6680         scanner.nexttok()
6681         if scanner.token == 'y':
6682             return True
6683         if scanner.token == 'n':
6684             return False
6685         scanner.chew()
6686         proutn(_("Please answer with \"y\" or \"n\": "))
6687
6688 def huh():
6689     "Complain about unparseable input."
6690     scanner.chew()
6691     skip(1)
6692     prout(_("Beg your pardon, Captain?"))
6693
6694 def debugme():
6695     "Access to the internals for debugging."
6696     proutn("Reset levels? ")
6697     if ja():
6698         if game.energy < game.inenrg:
6699             game.energy = game.inenrg
6700         game.shield = game.inshld
6701         game.torps = game.intorps
6702         game.lsupres = game.inlsr
6703     proutn("Reset damage? ")
6704     if ja():
6705         for i in range(NDEVICES):
6706             if game.damage[i] > 0.0:
6707                 game.damage[i] = 0.0
6708     proutn("Toggle debug flag? ")
6709     if ja():
6710         game.idebug = not game.idebug
6711         if game.idebug:
6712             prout("Debug output ON")
6713         else:
6714             prout("Debug output OFF")
6715     proutn("Cause selective damage? ")
6716     if ja():
6717         for i in range(NDEVICES):
6718             proutn("Kill %s?" % device[i])
6719             scanner.chew()
6720             key = scanner.nexttok()
6721             if key == "IHALPHA" and scanner.sees("y"):
6722                 game.damage[i] = 10.0
6723     proutn("Examine/change events? ")
6724     if ja():
6725         ev = Event()
6726         w = Coord()
6727         legends = {
6728             FSNOVA:  "Supernova       ",
6729             FTBEAM:  "T Beam          ",
6730             FSNAP:   "Snapshot        ",
6731             FBATTAK: "Base Attack     ",
6732             FCDBAS:  "Base Destroy    ",
6733             FSCMOVE: "SC Move         ",
6734             FSCDBAS: "SC Base Destroy ",
6735             FDSPROB: "Probe Move      ",
6736             FDISTR:  "Distress Call   ",
6737             FENSLV:  "Enslavement     ",
6738             FREPRO:  "Klingon Build   ",
6739         }
6740         for i in range(1, NEVENTS):
6741             proutn(legends[i])
6742             if is_scheduled(i):
6743                 proutn("%.2f" % (scheduled(i)-game.state.date))
6744                 if i in {FENSLV, FREPRO}:
6745                     ev = findevent(i)
6746                     proutn(" in %s" % ev.quadrant)
6747             else:
6748                 proutn("never")
6749             proutn("? ")
6750             scanner.chew()
6751             key = scanner.nexttok()
6752             if key == 'n':
6753                 unschedule(i)
6754                 scanner.chew()
6755             elif key == "IHREAL":
6756                 ev = schedule(i, scanner.real)
6757                 if i in {FENSLV, FREPRO}:
6758                     scanner.chew()
6759                     proutn("In quadrant- ")
6760                     key = scanner.nexttok()
6761                     # "IHEOL" says to leave coordinates as they are
6762                     if key != "IHEOL":
6763                         if key != "IHREAL":
6764                             prout("Event %d canceled, no x coordinate." % (i))
6765                             unschedule(i)
6766                             continue
6767                         w.i = int(round(scanner.real))
6768                         key = scanner.nexttok()
6769                         if key != "IHREAL":
6770                             prout("Event %d canceled, no y coordinate." % (i))
6771                             unschedule(i)
6772                             continue
6773                         w.j = int(round(scanner.real))
6774                         ev.quadrant = w
6775         scanner.chew()
6776     proutn("Induce supernova here? ")
6777     if ja():
6778         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6779         atover(True)
6780
6781 if __name__ == '__main__':
6782     try:
6783         #global line, thing, game
6784         game = None
6785         thing = Thingy()
6786         game = Gamestate()
6787         rnd = randomizer()
6788         logfp = None
6789         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6790         if os.getenv("TERM"):
6791             game.options |= OPTION_CURSES
6792         else:
6793             game.options |= OPTION_TTY
6794         seed = int(time.time())
6795         (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
6796         for (switch, val) in options:
6797             if switch == '-r':
6798                 # pylint: disable=raise-missing-from
6799                 try:
6800                     replayfp = open(val, "r")
6801                 except IOError:
6802                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6803                     raise SystemExit(1)
6804                 # pylint: disable=raise-missing-from
6805                 try:
6806                     line = replayfp.readline().strip()
6807                     (leader, __, seed) = line.split()
6808                     # pylint: disable=eval-used
6809                     seed = eval(seed)
6810                     line = replayfp.readline().strip()
6811                     arguments += line.split()[2:]
6812                 except ValueError:
6813                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6814                     raise SystemExit(1)
6815                 game.options |= OPTION_TTY
6816                 game.options &=~ OPTION_CURSES
6817             elif switch == '-s':
6818                 seed = int(val)
6819             elif switch == '-t':
6820                 game.options |= OPTION_TTY
6821                 game.options &=~ OPTION_CURSES
6822             elif switch == '-x':
6823                 game.idebug = True
6824             elif switch == '-c':        # Enable curses debugging - undocumented
6825                 game.cdebug = True
6826             elif switch == '-V':
6827                 print("SST2K", version)
6828                 raise SystemExit(0)
6829             else:
6830                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6831                 raise SystemExit(1)
6832         # where to save the input in case of bugs
6833         if "TMPDIR" in os.environ:
6834             tmpdir = os.environ['TMPDIR']
6835         else:
6836             tmpdir = "/tmp"
6837         try:
6838             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6839         except IOError:
6840             sys.stderr.write("sst: warning, can't open logfile\n")
6841             sys.exit(1)
6842         if logfp:
6843             logfp.write("# seed %s\n" % seed)
6844             logfp.write("# arguments %s\n" % " ".join(arguments))
6845             logfp.write("# SST2K version %s\n" % version)
6846             logfp.write("# recorded by %s@%s on %s\n" % \
6847                     (getpass.getuser(),socket.getfqdn(),time.ctime()))
6848         rnd.seed(seed)
6849         scanner = sstscanner()
6850         for arg in arguments:
6851             scanner.append(arg)
6852         try:
6853             iostart()
6854             while True: # Play a game
6855                 setwnd(fullscreen_window)
6856                 clrscr()
6857                 prelim()
6858                 setup()
6859                 if game.alldone:
6860                     score()
6861                     game.alldone = False
6862                 else:
6863                     makemoves()
6864                 if replayfp:
6865                     break
6866                 skip(1)
6867                 if (game.options & OPTION_TTY):
6868                     stars()
6869                 skip(1)
6870                 if game.tourn and game.alldone:
6871                     proutn(_("Do you want your score recorded?"))
6872                     if ja():
6873                         scanner.chew()
6874                         scanner.push("\n")
6875                         freeze(False)
6876                 scanner.chew()
6877                 if (game.options & OPTION_TTY):
6878                     proutn(_("Do you want to play again? "))
6879                     if not ja():
6880                         break
6881                 else:
6882                     break
6883             skip(1)
6884             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6885         finally:
6886             ioend()
6887         raise SystemExit(0)
6888     except KeyboardInterrupt:
6889         if logfp:
6890             logfp.close()
6891         print("")
6892
6893 # End.