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