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