Typo fix patch.
[super-star-trek.git] / sst.py
1 #!/usr/bin/env python3
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 from __future__ import print_function, division
15 # Runs under Python 2 an Python 3. Preserve this property!
16 # SPDX-License-Identifier: BSD-2-clause
17
18 # pylint: disable=line-too-long,superfluous-parens,too-many-lines,invalid-name,missing-function-docstring,missing-class-docstring,multiple-statements,too-many-branches,too-many-statements,too-many-locals,too-many-nested-blocks,too-many-return-statements,too-many-instance-attributes,global-statement,no-else-break,no-else-return,no-else-continue,too-few-public-methods,too-many-boolean-expressions,consider-using-f-string,consider-using-enumerate,consider-using-with,unspecified-encoding
19
20 # pylint: disable=multiple-imports
21 import os, sys, math, curses, time, pickle, copy, gettext, getpass
22 import getopt, socket, locale
23 import codecs
24
25 # This import only works on Unixes.  The intention is to enable
26 # Ctrl-P, Ctrl-N, and friends in Cmd.
27 try:
28     # pylint: disable=unused-import
29     import readline
30 except ImportError:
31     pass
32
33 # Prevent lossage under Python 3
34 try:
35     my_input = raw_input
36 except NameError:
37     my_input = input
38
39 version = "2.7"
40
41 docpath         = (".", "doc/", "/usr/share/doc/sst/")
42
43 def _(st):
44     return gettext.gettext(st)
45
46 # Rolling our own LCG because Python changed its incompatibly in 3.2.
47 # Thus, we need to have our own to be 2/3 polyglot, which will also
48 # be helpful when we forwrard-port.
49
50 class randomizer:
51     # LCG PRNG parameters tested against
52     # Knuth vol. 2. by the authors of ADVENT
53     LCG_A = 1093
54     LCG_C = 221587
55     LCG_M = 1048576
56
57     @staticmethod
58     def random():
59         old_x = game.lcg_x
60         game.lcg_x = (randomizer.LCG_A * game.lcg_x + randomizer.LCG_C) % randomizer.LCG_M
61         return old_x / randomizer.LCG_M
62
63     @staticmethod
64     def withprob(p):
65         v = randomizer.random()
66         #if logfp:
67         #    logfp.write("#withprob(%.2f) -> %s\n" % (p, v < p))
68         return v < p
69
70     @staticmethod
71     def integer(*args):
72         v = randomizer.random()
73         if len(args) == 1:
74             v = int(v * args[0])
75         else:
76             v = args[0] + int(v * (args[1] - args[0]))
77         #if logfp:
78         #    logfp.write("#integer%s -> %s\n" % (args, v))
79         return int(v)
80
81     @staticmethod
82     def real(*args):
83         v = randomizer.random()
84         if len(args) == 1:
85             v *= args[0]                 # from [0, args[0])
86         elif len(args) == 2:
87             v = args[0] + v*(args[1]-args[0])        # from [args[0], args[1])
88         #if logfp:
89         #    logfp.write("#real%s -> %f\n" % (args, v))
90         return v
91
92     @staticmethod
93     def seed(n):
94         #if logfp:
95         #    logfp.write("#seed(%d)\n" % n)
96         game.lcg_x = n % randomizer.LCG_M
97
98 GALSIZE         = 8             # Galaxy size in quadrants
99 NINHAB          = (GALSIZE * GALSIZE // 2)      # Number of inhabited worlds
100 MAXUNINHAB      = 10            # Maximum uninhabited worlds
101 QUADSIZE        = 10            # Quadrant size in sectors
102 BASEMIN         = 2                             # Minimum starbases
103 BASEMAX         = (GALSIZE * GALSIZE // 12)     # Maximum starbases
104 MAXKLGAME       = 127           # Maximum Klingons per game
105 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
106 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong
107 FOREVER         = 1e30          # Time for the indefinite future
108 MAXBURST        = 3             # Max # of torps you can launch in one turn
109 MINCMDR         = 10            # Minimum number of Klingon commanders
110 DOCKFAC         = 0.25          # Repair faster when docked
111 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
112
113 ALGERON         = 2311          # Date of the Treaty of Algeron
114
115
116 DEFAULT      = -1
117 BLACK        = 0
118 BLUE         = 1
119 GREEN        = 2
120 CYAN         = 3
121 RED          = 4
122 MAGENTA      = 5
123 BROWN        = 6
124 LIGHTGRAY    = 7
125 DARKGRAY     = 8
126 LIGHTBLUE    = 9
127 LIGHTGREEN   = 10
128 LIGHTCYAN    = 11
129 LIGHTRED     = 12
130 LIGHTMAGENTA = 13
131 YELLOW       = 14
132 WHITE        = 15
133
134 class TrekError(Exception):
135     pass
136
137 class JumpOut(Exception):
138     pass
139
140 class Coord:
141     def __init__(self, x=None, y=None):
142         self.i = x      # Row
143         self.j = y      # Column
144     def valid_quadrant(self):
145         return self.i >= 0 and self.i < GALSIZE and self.j >= 0 and self.j < GALSIZE
146     def valid_sector(self):
147         return self.i >= 0 and self.i < QUADSIZE and self.j >= 0 and self.j < QUADSIZE
148     def invalidate(self):
149         self.i = self.j = None
150     def __eq__(self, other):
151         return other is not None and self.i == other.i and self.j == other.j
152     def __ne__(self, other):
153         return other is None or self.i != other.i or self.j != other.j
154     def __add__(self, other):
155         return Coord(self.i+other.i, self.j+other.j)
156     def __sub__(self, other):
157         return Coord(self.i-other.i, self.j-other.j)
158     def __mul__(self, other):
159         return Coord(self.i*other, self.j*other)
160     def __rmul__(self, other):
161         return Coord(self.i*other, self.j*other)
162     def __div__(self, other):
163         return Coord(self.i/other, self.j/other)
164     def __truediv__(self, other):
165         return Coord(self.i/other, self.j/other)
166     def __floordiv__(self, other):
167         return Coord(self.i//other, self.j//other)
168     def __mod__(self, other):
169         return Coord(self.i % other, self.j % other)
170     def __rtruediv__(self, other):
171         return Coord(self.i/other, self.j/other)
172     def __rfloordiv__(self, other):
173         return Coord(self.i//other, self.j//other)
174     def roundtogrid(self):
175         return Coord(int(round(self.i)), int(round(self.j)))
176     def distance(self, other=None):
177         if not other:
178             other = Coord(0, 0)
179         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
180     def bearing(self):
181         return 1.90985*math.atan2(self.j, self.i)
182     def sgn(self):
183         s = Coord()
184         if self.i == 0:
185             s.i = 0
186         elif self.i < 0:
187             s.i = -1
188         else:
189             s.i = 1
190         if self.j == 0:
191             s.j = 0
192         elif self.j < 0:
193             s.j = -1
194         else:
195             s.j = 1
196         return s
197     def quadrant(self):
198         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
199         return self.roundtogrid() // QUADSIZE
200     def sector(self):
201         return self.roundtogrid() % QUADSIZE
202     def scatter(self):
203         s = Coord()
204         s.i = self.i + rnd.integer(-1, 2)
205         s.j = self.j + rnd.integer(-1, 2)
206         return s
207     def __str__(self):
208         if self.i is None or self.j is None:
209             return "Nowhere"
210         return "%s - %s" % (self.i+1, self.j+1)
211     __repr__ = __str__
212
213 class Thingy(Coord):
214     "Do not anger the Space Thingy!"
215     def __init__(self):
216         Coord.__init__(self)
217         self.angered = False
218     def angry(self):
219         self.angered = True
220     def at(self, q):
221         return (q.i, q.j) == (self.i, self.j)
222
223 class Planet:
224     def __init__(self):
225         self.name = None        # string-valued if inhabited
226         self.quadrant = Coord()        # quadrant located
227         self.pclass = None        # could be ""M", "N", "O", or "destroyed"
228         self.crystals = "absent"# could be "mined", "present", "absent"
229         self.known = "unknown"        # could be "unknown", "known", "shuttle_down"
230         self.inhabited = False        # is it inhabited?
231     def __str__(self):
232         return self.name
233
234 class Quadrant:
235     def __init__(self):
236         self.stars = 0
237         self.planet = None
238         self.starbase = False
239         self.klingons = 0
240         self.romulans = 0
241         self.supernova = False
242         self.charted = False
243         self.status = "secure"        # Could be "secure", "distressed", "enslaved"
244     def __str__(self):
245         return "<Quadrant: %(klingons)d>" % self.__dict__
246     __repr__ = __str__
247
248 class Page:
249     def __init__(self):
250         self.stars = None
251         self.starbase = False
252         self.klingons = None
253     def __repr__(self):
254         return "<%s,%s,%s>" % (self.klingons, self.starbase, self.stars)
255
256 def fill2d(size, fillfun):
257     "Fill an empty list in 2D."
258     lst = []
259     for i in range(size):
260         lst.append([])
261         for j in range(size):
262             lst[i].append(fillfun(i, j))
263     return lst
264
265 class Snapshot:
266     def __init__(self):
267         self.snap = False       # snapshot taken
268         self.crew = 0           # crew complement
269         self.nscrem = 0         # remaining super commanders
270         self.starkl = 0         # destroyed stars
271         self.basekl = 0         # destroyed bases
272         self.nromrem = 0        # Romulans remaining
273         self.nplankl = 0        # destroyed uninhabited planets
274         self.nworldkl = 0        # destroyed inhabited planets
275         self.planets = []        # Planet information
276         self.date = 0.0           # stardate
277         self.remres = 0         # remaining resources
278         self.remtime = 0        # remaining time
279         self.baseq = []         # Base quadrant coordinates
280         self.kcmdr = []         # Commander quadrant coordinates
281         self.kscmdr = Coord()        # Supercommander quadrant coordinates
282         # the galaxy
283         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
284         # the starchart
285         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
286     def traverse(self):
287         for i in range(GALSIZE):
288             for j in range(GALSIZE):
289                 yield (i, j, self.galaxy[i][j])
290
291 class Event:
292     def __init__(self):
293         self.date = None        # A real number
294         self.quadrant = None        # A coord structure
295
296 # game options
297 OPTION_ALL        = 0xffffffff
298 OPTION_TTY        = 0x00000001        # old interface
299 OPTION_CURSES     = 0x00000002        # new interface
300 OPTION_IOMODES    = 0x00000003        # cover both interfaces
301 OPTION_PLANETS    = 0x00000004        # planets and mining
302 OPTION_THOLIAN    = 0x00000008        # Tholians and their webs (UT 1979 version)
303 OPTION_THINGY     = 0x00000010        # Space Thingy can shoot back (Stas, 2005)
304 OPTION_PROBE      = 0x00000020        # deep-space probes (DECUS version, 1980)
305 OPTION_SHOWME     = 0x00000040        # bracket Enterprise in chart
306 OPTION_RAMMING    = 0x00000080        # enemies may ram Enterprise (Almy)
307 OPTION_MVBADDY    = 0x00000100        # more enemies can move (Almy)
308 OPTION_BLKHOLE    = 0x00000200        # black hole may timewarp you (Stas, 2005)
309 OPTION_BASE       = 0x00000400        # bases have good shields (Stas, 2005)
310 OPTION_WORLDS     = 0x00000800        # logic for inhabited worlds (ESR, 2006)
311 OPTION_AUTOSCAN   = 0x00001000        # automatic LRSCAN before CHART (ESR, 2006)
312 OPTION_CAPTURE    = 0x00002000        # Enable BSD-Trek capture (Almy, 2013).
313 OPTION_CLOAK      = 0x80004000        # Enable BSD-Trek capture (Almy, 2013).
314 OPTION_PLAIN      = 0x01000000        # user chose plain game
315 OPTION_ALMY       = 0x02000000        # user chose Almy variant
316 OPTION_COLOR      = 0x04000000        # enable color display (ESR, 2010)
317 OPTION_DOTFILL    = 0x08000000        # fix dotfill glitch in chart (ESR, 2019)
318
319 # Define devices
320 DSRSENS         = 0
321 DLRSENS         = 1
322 DPHASER         = 2
323 DPHOTON         = 3
324 DLIFSUP         = 4
325 DWARPEN         = 5
326 DIMPULS         = 6
327 DSHIELD         = 7
328 DRADIO          = 8
329 DSHUTTL         = 9
330 DCOMPTR         = 10
331 DNAVSYS         = 11
332 DTRANSP         = 12
333 DSHCTRL         = 13
334 DDRAY           = 14
335 DDSP            = 15
336 DCLOAK          = 16
337 NDEVICES        = 17        # Number of devices
338
339 SKILL_NONE      = 0
340 SKILL_NOVICE    = 1
341 SKILL_FAIR      = 2
342 SKILL_GOOD      = 3
343 SKILL_EXPERT    = 4
344 SKILL_EMERITUS  = 5
345
346 def damaged(dev):
347     return (game.damage[dev] != 0.0)
348 def communicating():
349     return not damaged(DRADIO) or game.condition=="docked"
350
351 # Define future events
352 FSPY    = 0        # Spy event happens always (no future[] entry)
353                    # can cause SC to tractor beam Enterprise
354 FSNOVA  = 1        # Supernova
355 FTBEAM  = 2        # Commander tractor beams Enterprise
356 FSNAP   = 3        # Snapshot for time warp
357 FBATTAK = 4        # Commander attacks base
358 FCDBAS  = 5        # Commander destroys base
359 FSCMOVE = 6        # Supercommander moves (might attack base)
360 FSCDBAS = 7        # Supercommander destroys base
361 FDSPROB = 8        # Move deep space probe
362 FDISTR  = 9        # Emit distress call from an inhabited world
363 FENSLV  = 10       # Inhabited word is enslaved
364 FREPRO  = 11       # Klingons build a ship in an enslaved system
365 NEVENTS = 12
366
367 # Abstract out the event handling -- underlying data structures will change
368 # when we implement stateful events
369 def findevent(evtype):
370     return game.future[evtype]
371
372 class Enemy:
373     def __init__(self, etype=None, loc=None, power=None):
374         self.type = etype
375         self.location = Coord()
376         self.kdist = None
377         self.kavgd = None
378         if loc:
379             self.move(loc)
380         self.power = power        # enemy energy level
381         game.enemies.append(self)
382     def move(self, loc):
383         motion = (loc != self.location)
384         if self.location.i is not None and self.location.j is not None:
385             if motion:
386                 if self.type == 'T':
387                     game.quad[self.location.i][self.location.j] = '#'
388                 else:
389                     game.quad[self.location.i][self.location.j] = '.'
390         if loc:
391             self.location = copy.copy(loc)
392             game.quad[self.location.i][self.location.j] = self.type
393             self.kdist = self.kavgd = (game.sector - loc).distance()
394         else:
395             self.location = Coord()
396             self.kdist = self.kavgd = None
397             # Guard prevents failure on Tholian or thingy
398             if self in game.enemies:
399                 game.enemies.remove(self)
400         return motion
401     def __repr__(self):
402         return "<%s,%s.%f>" % (self.type, self.location, self.power)        # For debugging
403
404 class Gamestate:
405     def __init__(self):
406         self.options = None        # Game options
407         self.state = Snapshot()        # A snapshot structure
408         self.snapsht = Snapshot()        # Last snapshot taken for time-travel purposes
409         self.quad = None        # contents of our quadrant
410         self.damage = [0.0] * NDEVICES        # damage encountered
411         self.future = []        # future events
412         i = NEVENTS
413         while i > 0:
414             i -= 1
415             self.future.append(Event())
416         self.passwd  = None        # Self Destruct password
417         self.enemies = []
418         self.quadrant = None        # where we are in the large
419         self.sector = None        # where we are in the small
420         self.tholian = None        # Tholian enemy object
421         self.base = None        # position of base in current quadrant
422         self.battle = None        # base coordinates being attacked
423         self.plnet = None        # location of planet in quadrant
424         self.gamewon = False        # Finished!
425         self.ididit = False        # action taken -- allows enemy to attack
426         self.alive = False        # we are alive (not killed)
427         self.justin = False        # just entered quadrant
428         self.shldup = False        # shields are up
429         self.shldchg = False        # shield is changing (affects efficiency)
430         self.iscate = False        # super commander is here
431         self.ientesc = False        # attempted escape from supercommander
432         self.resting = False        # rest time
433         self.icraft = False        # Kirk in Galileo
434         self.landed = False        # party on planet (true), on ship (false)
435         self.alldone = False        # game is now finished
436         self.neutz = False        # Romulan Neutral Zone
437         self.isarmed = False        # probe is armed
438         self.inorbit = False        # orbiting a planet
439         self.imine = False        # mining
440         self.icrystl = False        # dilithium crystals aboard
441         self.iseenit = False        # seen base attack report
442         self.thawed = False        # thawed game
443         self.condition = None        # "green", "yellow", "red", "docked", "dead"
444         self.iscraft = None        # "onship", "offship", "removed"
445         self.skill = SKILL_NONE        # Player skill level
446         self.inkling = 0        # initial number of klingons
447         self.inbase = 0                # initial number of bases
448         self.incom = 0                # initial number of commanders
449         self.inscom = 0                # initial number of commanders
450         self.inrom = 0                # initial number of commanders
451         self.instar = 0                # initial stars
452         self.intorps = 0        # initial/max torpedoes
453         self.torps = 0                # number of torpedoes
454         self.ship = 0                # ship type -- 'E' is Enterprise
455         self.abandoned = 0        # count of crew abandoned in space
456         self.length = 0                # length of game
457         self.klhere = 0                # klingons here
458         self.casual = 0                # causalties
459         self.nhelp = 0                # calls for help
460         self.nkinks = 0                # count of energy-barrier crossings
461         self.iplnet = None        # planet # in quadrant
462         self.inplan = 0                # initial planets
463         self.irhere = 0                # Romulans in quadrant
464         self.isatb = 0                # =2 if super commander is attacking base
465         self.tourn = None        # tournament number
466         self.nprobes = 0        # number of probes available
467         self.inresor = 0.0        # initial resources
468         self.intime = 0.0        # initial time
469         self.inenrg = 0.0        # initial/max energy
470         self.inshld = 0.0        # initial/max shield
471         self.inlsr = 0.0        # initial life support resources
472         self.indate = 0.0        # initial date
473         self.energy = 0.0        # energy level
474         self.shield = 0.0        # shield level
475         self.warpfac = 0.0        # warp speed
476         self.lsupres = 0.0        # life support reserves
477         self.optime = 0.0        # time taken by current operation
478         self.damfac = 0.0        # damage factor
479         self.lastchart = 0.0        # time star chart was last updated
480         self.cryprob = 0.0        # probability that crystal will work
481         self.probe = None        # object holding probe course info
482         self.height = 0.0        # height of orbit around planet
483         self.score = 0.0        # overall score
484         self.perdate = 0.0        # rate of kills
485         self.idebug = False        # Debugging instrumentation enabled?
486         self.cdebug = False        # Debugging instrumentation for curses enabled?
487         self.statekscmdr = None # No SuperCommander coordinates yet.
488         self.brigcapacity = 400     # Enterprise brig capacity
489         self.brigfree = 400       # How many klingons can we put in the brig?
490         self.kcaptured = 0      # Total Klingons captured, for scoring.
491         self.iscloaked = False  # Cloaking device on?
492         self.ncviol = 0         # Algreon treaty violations
493         self.isviolreported = False # We have been warned
494         self.lcg_x = 0          # LCG generator value
495     def remkl(self):
496         return sum([q.klingons for (_i, _j, q) in list(self.state.traverse())])
497     def recompute(self):
498         # Stas thinks this should be (C expression):
499         # game.remkl() + len(game.state.kcmdr) > 0 ?
500         #        game.state.remres/(game.remkl() + 4*len(game.state.kcmdr)) : 99
501         # He says the existing expression is prone to divide-by-zero errors
502         # after killing the last klingon when score is shown -- perhaps also
503         # if the only remaining klingon is SCOM.
504         self.state.remtime = self.state.remres/(self.remkl() + 4*len(self.state.kcmdr))
505     def unwon(self):
506         "Are there Klingons remaining?"
507         return self.remkl()
508
509 FWON = 0
510 FDEPLETE = 1
511 FLIFESUP = 2
512 FNRG = 3
513 FBATTLE = 4
514 FNEG3 = 5
515 FNOVA = 6
516 FSNOVAED = 7
517 FABANDN = 8
518 FDILITHIUM = 9
519 FMATERIALIZE = 10
520 FPHASER = 11
521 FLOST = 12
522 FMINING = 13
523 FDPLANET = 14
524 FPNOVA = 15
525 FSSC = 16
526 FSTRACTOR = 17
527 FDRAY = 18
528 FTRIBBLE = 19
529 FHOLE = 20
530 FCREW = 21
531 FCLOAK = 22
532
533 # Code from ai.c begins here
534
535 def welcoming(iq):
536     "Would this quadrant welcome another Klingon?"
537     return iq.valid_quadrant() and \
538         not game.state.galaxy[iq.i][iq.j].supernova and \
539         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
540
541 def tryexit(enemy, look, irun):
542     "A bad guy attempts to bug out."
543     iq = Coord()
544     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))//QUADSIZE - 1
545     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))//QUADSIZE - 1
546     if not welcoming(iq):
547         return []
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)
1331                     displacement.nexttok()
1332                     bumpto = displacement.sector()
1333                     if not bumpto.valid_sector():
1334                         prout(_(" damaged but not destroyed."))
1335                         return None
1336                     if game.quad[bumpto.i][bumpto.j] == ' ':
1337                         prout(_(" buffeted into black hole."))
1338                         deadkl(w, iquad, bumpto)
1339                     if game.quad[bumpto.i][bumpto.j] != '.':
1340                         prout(_(" damaged but not destroyed."))
1341                     else:
1342                         prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1343                         enemy.location = bumpto
1344                         game.quad[w.i][w.j] = '.'
1345                         game.quad[bumpto.i][bumpto.j] = iquad
1346                         for tenemy in game.enemies:
1347                             tenemy.kdist = tenemy.kavgd = (game.sector-tenemy.location).distance()
1348                         sortenemies()
1349                     break
1350             else:
1351                 prout("Internal error, no enemy where expected!")
1352                 raise SystemExit(1)
1353             return None
1354         elif iquad == 'B': # Hit a base
1355             skip(1)
1356             prout(_("***STARBASE DESTROYED.."))
1357             game.state.baseq = [x for x in game.state.baseq if x != game.quadrant]
1358             game.quad[w.i][w.j] = '.'
1359             game.base.invalidate()
1360             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
1361             game.state.chart[game.quadrant.i][game.quadrant.j].starbase = False
1362             game.state.basekl += 1
1363             newcnd()
1364             return None
1365         elif iquad == 'P': # Hit a planet
1366             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1367             game.state.nplankl += 1
1368             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1369             game.iplnet.pclass = "destroyed"
1370             game.iplnet = None
1371             game.plnet.invalidate()
1372             game.quad[w.i][w.j] = '.'
1373             if game.landed:
1374                 # captain perishes on planet
1375                 finish(FDPLANET)
1376             return None
1377         elif iquad == '@': # Hit an inhabited world -- very bad!
1378             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1379             game.state.nworldkl += 1
1380             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1381             game.iplnet.pclass = "destroyed"
1382             game.iplnet = None
1383             game.plnet.invalidate()
1384             game.quad[w.i][w.j] = '.'
1385             if game.landed:
1386                 # captain perishes on planet
1387                 finish(FDPLANET)
1388             prout(_("The torpedo destroyed an inhabited planet."))
1389             return None
1390         elif iquad == '*': # Hit a star
1391             if rnd.withprob(0.9):
1392                 nova(w)
1393             else:
1394                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1395             return None
1396         elif iquad == '?': # Hit a thingy
1397             if not (game.options & OPTION_THINGY) or rnd.withprob(0.3):
1398                 skip(1)
1399                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1400                 skip(1)
1401                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1402                 skip(1)
1403                 proutn(_("Mr. Spock-"))
1404                 prouts(_("  \"Fascinating!\""))
1405                 skip(1)
1406                 deadkl(w, iquad, w)
1407             else:
1408                 # Stas Sergeev added the possibility that
1409                 # you can shove the Thingy and piss it off.
1410                 # It then becomes an enemy and may fire at you.
1411                 thing.angry()
1412             return None
1413         elif iquad == ' ': # Black hole
1414             skip(1)
1415             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1416             return None
1417         elif iquad == '#': # hit the web
1418             skip(1)
1419             prout(_("***Torpedo absorbed by Tholian web."))
1420             return None
1421         elif iquad == 'T':  # Hit a Tholian
1422             h1 = 700.0 + rnd.integer(100) - \
1423                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1424             h1 = math.fabs(h1)
1425             if h1 >= 600:
1426                 game.quad[w.i][w.j] = '.'
1427                 deadkl(w, iquad, w)
1428                 game.tholian = None
1429                 return None
1430             skip(1)
1431             proutn(crmena(True, 'T', "sector", w))
1432             if rnd.withprob(0.05):
1433                 prout(_(" survives photon blast."))
1434                 return None
1435             prout(_(" disappears."))
1436             game.tholian.move(None)
1437             game.quad[w.i][w.j] = '#'
1438             dropin(' ')
1439             return None
1440         else: # Problem!
1441             skip(1)
1442             proutn("Don't know how to handle torpedo collision with ")
1443             proutn(crmena(True, iquad, "sector", w))
1444             skip(1)
1445             return None
1446         break
1447     skip(1)
1448     setwnd(message_window)
1449     prout(_("Torpedo missed."))
1450     return None
1451
1452 def fry(hit):
1453     "Critical-hit resolution."
1454     if hit < (275.0-25.0*game.skill)*rnd.real(1.0, 1.5):
1455         return
1456     ncrit = int(1.0 + hit/(500.0+rnd.real(100)))
1457     proutn(_("***CRITICAL HIT--"))
1458     # Select devices and cause damage
1459     cdam = []
1460     while ncrit > 0:
1461         while True:
1462             j = randdevice()
1463             # Cheat to prevent shuttle damage unless on ship
1464             if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship") or (j == DCLOAK and game.ship != 'E' or j == DDRAY)):
1465                 break
1466         cdam.append(j)
1467         extradm = (hit*game.damfac)/(ncrit*rnd.real(75, 100))
1468         game.damage[j] += extradm
1469         ncrit -= 1
1470     skipcount = 0
1471     for (i, j) in enumerate(cdam):
1472         proutn(device[j])
1473         if skipcount % 3 == 2 and i < len(cdam)-1:
1474             skip(1)
1475         skipcount += 1
1476         if i < len(cdam)-1:
1477             proutn(_(" and "))
1478     prout(_(" damaged."))
1479     if damaged(DSHIELD) and game.shldup:
1480         prout(_("***Shields knocked down."))
1481         game.shldup = False
1482     if damaged(DCLOAK) and game.iscloaked:
1483         prout(_("***Cloaking device rendered inoperative."))
1484         game.iscloaked = False
1485
1486 def attack(torps_ok):
1487     # bad guy attacks us
1488     # torps_ok == False forces use of phasers in an attack
1489     if game.iscloaked:
1490         return
1491     # game could be over at this point, check
1492     if game.alldone:
1493         return
1494     attempt = False
1495     ihurt = False
1496     hitmax = 0.0
1497     hittot = 0.0
1498     chgfac = 1.0
1499     where = "neither"
1500     if game.idebug:
1501         prout("=== ATTACK!")
1502     # Tholian gets to move before attacking
1503     if game.tholian:
1504         movetholian()
1505     # if you have just entered the RNZ, you'll get a warning
1506     if game.neutz: # The one chance not to be attacked
1507         game.neutz = False
1508         return
1509     # commanders get a chance to tac-move towards you
1510     if (((game.quadrant in game.state.kcmdr or game.state.kscmdr == game.quadrant) and not game.justin) or game.skill == SKILL_EMERITUS) and torps_ok:
1511         for (bugout, enemy, old, goto) in  moveklings():
1512             if bugout:
1513                 # we know about this if either short or long range
1514                 # sensors are working
1515                 if damaged(DSRSENS) and damaged(DLRSENS) \
1516                        and game.condition != "docked":
1517                     prout(crmena(True, enemy.type, "sector", old) + \
1518                           (_(" escapes to Quadrant %s (and regains strength).") % goto))
1519             else: # Enemy still in-sector
1520                 if enemy.move(goto):
1521                     if not damaged(DSRSENS) or game.condition == "docked":
1522                         proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
1523                         if enemy.kdist < old:
1524                             proutn(_(" advances to "))
1525                         else:
1526                             proutn(_(" retreats to "))
1527                         prout("Sector %s." % goto)
1528         sortenemies()
1529     # if no enemies remain after movement, we're done
1530     if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing.at(game.quadrant) and not thing.angered):
1531         return
1532     # set up partial hits if attack happens during shield status change
1533     pfac = 1.0/game.inshld
1534     if game.shldchg:
1535         chgfac = 0.25 + rnd.real(0.5)
1536     skip(1)
1537     # message verbosity control
1538     if game.skill <= SKILL_FAIR:
1539         where = "sector"
1540     for enemy in game.enemies:
1541         if enemy.power < 0:
1542             continue        # too weak to attack
1543         # compute hit strength and diminish shield power
1544         r = rnd.real()
1545         # Increase chance of photon torpedos if docked or enemy energy is low
1546         if game.condition == "docked":
1547             r *= 0.25
1548         if enemy.power < 500:
1549             r *= 0.25
1550         if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
1551             continue
1552         # different enemies have different probabilities of throwing a torp
1553         usephasers = not torps_ok or \
1554             (enemy.type == 'K' and r > 0.0005) or \
1555             (enemy.type == 'C' and r > 0.015) or \
1556             (enemy.type == 'R' and r > 0.3) or \
1557             (enemy.type == 'S' and r > 0.07) or \
1558             (enemy.type == '?' and r > 0.05)
1559         if usephasers:            # Enemy uses phasers
1560             if game.condition == "docked":
1561                 continue # Don't waste the effort!
1562             attempt = True # Attempt to attack
1563             dustfac = rnd.real(0.8, 0.85)
1564             hit = enemy.power*math.pow(dustfac, enemy.kavgd)
1565             enemy.power *= 0.75
1566         else: # Enemy uses photon torpedo
1567             # We should be able to make the bearing() method work here
1568             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1569             hit = 0
1570             proutn(_("***TORPEDO INCOMING"))
1571             if not damaged(DSRSENS):
1572                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1573             attempt = True
1574             prout("  ")
1575             dispersion = (rnd.real()+rnd.real())*0.5 - 0.5
1576             dispersion += 0.002*enemy.power*dispersion
1577             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1578             if game.unwon() == 0:
1579                 finish(FWON) # Klingons did themselves in!
1580             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1581                 return # Supernova or finished
1582             if hit is None:
1583                 continue
1584         # incoming phaser or torpedo, shields may dissipate it
1585         if game.shldup or game.shldchg or game.condition == "docked":
1586             # shields will take hits
1587             propor = pfac * game.shield
1588             if game.condition == "docked":
1589                 propor *= 2.1
1590             propor = max(propor, 0.1)
1591             hitsh = propor*chgfac*hit+1.0
1592             absorb = 0.8*hitsh
1593             if absorb > game.shield:
1594                 absorb = game.shield
1595             game.shield -= absorb
1596             hit -= hitsh
1597             # taking a hit blasts us out of a starbase dock
1598             if game.condition == "docked":
1599                 dock(False)
1600             # but the shields may take care of it
1601             if propor > 0.1 and hit < 0.005*game.energy:
1602                 continue
1603         # hit from this opponent got through shields, so take damage
1604         ihurt = True
1605         proutn(_("%d unit hit") % int(hit))
1606         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1607             proutn(_(" on the ") + crmshp())
1608         if not damaged(DSRSENS) and usephasers:
1609             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1610         skip(1)
1611         # Decide if hit is critical
1612         if hit > hitmax:
1613             hitmax = hit
1614         hittot += hit
1615         fry(hit)
1616         game.energy -= hit
1617     if game.energy <= 0:
1618         # Returning home upon your shield, not with it...
1619         finish(FBATTLE)
1620         return
1621     if not attempt and game.condition == "docked":
1622         prout(_("***Enemies decide against attacking your ship."))
1623     percent = 100.0*pfac*game.shield+0.5
1624     if not ihurt:
1625         # Shields fully protect ship
1626         proutn(_("Enemy attack reduces shield strength to "))
1627     else:
1628         # Emit message if starship suffered hit(s)
1629         skip(1)
1630         proutn(_("Energy left %2d    shields ") % int(game.energy))
1631         if game.shldup:
1632             proutn(_("up "))
1633         elif not damaged(DSHIELD):
1634             proutn(_("down "))
1635         else:
1636             proutn(_("damaged, "))
1637     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1638     # Check if anyone was hurt
1639     if hitmax >= 200 or hittot >= 500:
1640         icas = rnd.integer(int(hittot * 0.015))
1641         if icas >= 2:
1642             skip(1)
1643             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1644             prout(_("   in that last attack.\""))
1645             game.casual += icas
1646             game.state.crew -= icas
1647     # After attack, reset average distance to enemies
1648     for enemy in game.enemies:
1649         enemy.kavgd = enemy.kdist
1650     sortenemies()
1651     return
1652
1653 def deadkl(w, etype, mv):
1654     "Kill a Klingon, Tholian, Romulan, or Thingy."
1655     # Added mv to allow enemy to "move" before dying
1656     proutn(crmena(True, etype, "sector", mv))
1657     # Decide what kind of enemy it is and update appropriately
1658     if etype == 'R':
1659         # Chalk up a Romulan
1660         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1661         game.irhere -= 1
1662         game.state.nromrem -= 1
1663     elif etype == 'T':
1664         # Killed a Tholian
1665         game.tholian = None
1666     elif etype == '?':
1667         # Killed a Thingy
1668         global thing
1669         thing = None
1670     else:
1671         # Killed some type of Klingon
1672         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1673         game.klhere -= 1
1674         if etype == 'C':
1675             game.state.kcmdr.remove(game.quadrant)
1676             unschedule(FTBEAM)
1677             if game.state.kcmdr:
1678                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1679             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1680                 unschedule(FCDBAS)
1681         elif etype ==  'K':
1682             pass
1683         elif etype ==  'S':
1684             game.state.nscrem -= 1
1685             game.state.kscmdr.invalidate()
1686             game.isatb = 0
1687             game.iscate = False
1688             unschedule(FSCMOVE)
1689             unschedule(FSCDBAS)
1690     # For each kind of enemy, finish message to player
1691     prout(_(" destroyed."))
1692     if game.unwon() == 0:
1693         return
1694     game.recompute()
1695     # Remove enemy ship from arrays describing local conditions
1696     for e in game.enemies:
1697         if e.location == w:
1698             e.move(None)
1699             break
1700     return
1701
1702 def targetcheck(w):
1703     "Return None if target is invalid, otherwise return a course angle."
1704     if not w.valid_sector():
1705         huh()
1706         return None
1707     delta = Coord()
1708     # C code this was translated from is wacky -- why the sign reversal?
1709     delta.j = (w.j - game.sector.j)
1710     delta.i = (game.sector.i - w.i)
1711     if delta == Coord(0, 0):
1712         skip(1)
1713         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1714         prout(_("  I recommend an immediate review of"))
1715         prout(_("  the Captain's psychological profile.\""))
1716         scanner.chew()
1717         return None
1718     return delta.bearing()
1719
1720 def torps():
1721     "Launch photon torpedo salvo."
1722     tcourse = []
1723     game.ididit = False
1724     if damaged(DPHOTON):
1725         prout(_("Photon tubes damaged."))
1726         scanner.chew()
1727         return
1728     if game.torps == 0:
1729         prout(_("No torpedoes left."))
1730         scanner.chew()
1731         return
1732     # First, get torpedo count
1733     while True:
1734         scanner.nexttok()
1735         if scanner.token == "IHALPHA":
1736             huh()
1737             return
1738         elif scanner.token == "IHEOL" or not scanner.waiting():
1739             prout(_("%d torpedoes left.") % game.torps)
1740             scanner.chew()
1741             proutn(_("Number of torpedoes to fire- "))
1742             continue        # Go back around to get a number
1743         else: # key == "IHREAL"
1744             try:
1745                 n = scanner.int()
1746             except TypeError:
1747                 huh()
1748                 return
1749             if n <= 0: # abort command
1750                 scanner.chew()
1751                 return
1752             if n > MAXBURST:
1753                 scanner.chew()
1754                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1755                 return
1756             if n > game.torps:
1757                 scanner.chew()        # User requested more torps than available
1758                 continue        # Go back around
1759             break        # All is good, go to next stage
1760     # Next, get targets
1761     target = []
1762     for i in range(n):
1763         key = scanner.nexttok()
1764         if i == 0 and key == "IHEOL":
1765             break        # no coordinate waiting, we will try prompting
1766         if i == 1 and key == "IHEOL":
1767             # direct all torpedoes at one target
1768             while i < n:
1769                 target.append(target[0])
1770                 tcourse.append(tcourse[0])
1771                 i += 1
1772             break
1773         scanner.push(scanner.token)
1774         target.append(scanner.getcoord())
1775         if target[-1] is None:
1776             return
1777         tcourse.append(targetcheck(target[-1]))
1778         if tcourse[-1] is None:
1779             return
1780     scanner.chew()
1781     if len(target) == 0:
1782         # prompt for each one
1783         for i in range(n):
1784             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1785             scanner.chew()
1786             target.append(scanner.getcoord())
1787             if target[-1] is None:
1788                 return
1789             tcourse.append(targetcheck(target[-1]))
1790             if tcourse[-1] is None:
1791                 return
1792     game.ididit = True
1793     # Loop for moving <n> torpedoes
1794     for i in range(n):
1795         if game.condition != "docked":
1796             game.torps -= 1
1797         dispersion = (rnd.real()+rnd.real())*0.5 -0.5
1798         if math.fabs(dispersion) >= 0.47:
1799             # misfire!
1800             dispersion *= rnd.real(1.2, 2.2)
1801             if n > 0:
1802                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1803             else:
1804                 prouts(_("***TORPEDO MISFIRES."))
1805             skip(1)
1806             if i < n:
1807                 prout(_("  Remainder of burst aborted."))
1808             if rnd.withprob(0.2):
1809                 prout(_("***Photon tubes damaged by misfire."))
1810                 game.damage[DPHOTON] = game.damfac * rnd.real(1.0, 3.0)
1811             break
1812         if game.iscloaked:
1813             dispersion *= 1.2
1814         elif game.shldup or game.condition == "docked":
1815             dispersion *= 1.0 + 0.0001*game.shield
1816         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1817         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1818             return
1819     if game.unwon()<=0:
1820         finish(FWON)
1821
1822 def overheat(rpow):
1823     "Check for phasers overheating."
1824     if rpow > 1500:
1825         checkburn = (rpow-1500.0)*0.00038
1826         if rnd.withprob(checkburn):
1827             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1828             game.damage[DPHASER] = game.damfac* rnd.real(1.0, 2.0) * (1.0+checkburn)
1829
1830 def checkshctrl(rpow):
1831     "Check shield control."
1832     skip(1)
1833     if rnd.withprob(0.998):
1834         prout(_("Shields lowered."))
1835         return False
1836     # Something bad has happened
1837     prouts(_("***RED ALERT!  RED ALERT!"))
1838     skip(2)
1839     hit = rpow*game.shield/game.inshld
1840     game.energy -= rpow+hit*0.8
1841     game.shield -= hit*0.2
1842     if game.energy <= 0.0:
1843         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1844         skip(1)
1845         stars()
1846         finish(FPHASER)
1847         return True
1848     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1849     skip(2)
1850     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1851     icas = rnd.integer(int(hit*0.012))
1852     skip(1)
1853     fry(0.8*hit)
1854     if icas:
1855         skip(1)
1856         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1857         prout(_("  %d casualties so far.\"") % icas)
1858         game.casual += icas
1859         game.state.crew -= icas
1860     skip(1)
1861     prout(_("Phaser energy dispersed by shields."))
1862     prout(_("Enemy unaffected."))
1863     overheat(rpow)
1864     return True
1865
1866 def hittem(hits):
1867     "Register a phaser hit on Klingons and Romulans."
1868     w = Coord()
1869     skip(1)
1870     kk = 0
1871     for wham in hits:
1872         if wham == 0:
1873             continue
1874         dustfac = rnd.real(0.9, 1.0)
1875         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1876         kpini = game.enemies[kk].power
1877         kp = math.fabs(kpini)
1878         if PHASEFAC*hit < kp:
1879             kp = PHASEFAC*hit
1880         if game.enemies[kk].power < 0:
1881             game.enemies[kk].power -= -kp
1882         else:
1883             game.enemies[kk].power -= kp
1884         kpow = game.enemies[kk].power
1885         w = game.enemies[kk].location
1886         if hit > 0.005:
1887             if not damaged(DSRSENS):
1888                 boom(w)
1889             proutn(_("%d unit hit on ") % int(hit))
1890         else:
1891             proutn(_("Very small hit on "))
1892         ienm = game.quad[w.i][w.j]
1893         if ienm == '?':
1894             thing.angry()
1895         proutn(crmena(False, ienm, "sector", w))
1896         skip(1)
1897         if kpow == 0:
1898             deadkl(w, ienm, w)
1899             if game.unwon()==0:
1900                 finish(FWON)
1901             if game.alldone:
1902                 return
1903             continue
1904         else: # decide whether or not to emasculate klingon
1905             # pylint: disable=chained-comparison
1906             if kpow > 0 and rnd.withprob(0.9) and kpow <= rnd.real(0.4, 0.8)*kpini:
1907                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1908                 prout(_("   has just lost its firepower.\""))
1909                 game.enemies[kk].power = -kpow
1910         kk += 1
1911     return
1912
1913 def phasers():
1914     "Fire phasers at bad guys."
1915     hits = []
1916     kz = 0
1917     k = 1
1918     irec = 0 # Cheating inhibitor
1919     ifast = False
1920     no = False
1921     itarg = True
1922     msgflag = True
1923     rpow = 0.0
1924     automode = "NOTSET"
1925     key = ""
1926     skip(1)
1927     # SR sensors and Computer are needed for automode
1928     if damaged(DSRSENS) or damaged(DCOMPTR):
1929         itarg = False
1930     if game.condition == "docked":
1931         prout(_("Phasers can't be fired through base shields."))
1932         scanner.chew()
1933         return
1934     if damaged(DPHASER):
1935         prout(_("Phaser control damaged."))
1936         scanner.chew()
1937         return
1938     if game.shldup:
1939         if damaged(DSHCTRL):
1940             prout(_("High speed shield control damaged."))
1941             scanner.chew()
1942             return
1943         if game.energy <= 200.0:
1944             prout(_("Insufficient energy to activate high-speed shield control."))
1945             scanner.chew()
1946             return
1947         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1948         ifast = True
1949     # Original code so convoluted, I re-did it all
1950     # (That was Tom Almy talking about the C code, I think -- ESR)
1951     while automode == "NOTSET":
1952         key = scanner.nexttok()
1953         if key == "IHALPHA":
1954             if scanner.sees("manual"):
1955                 if len(game.enemies)==0:
1956                     prout(_("There is no enemy present to select."))
1957                     scanner.chew()
1958                     key = "IHEOL"
1959                     automode = "AUTOMATIC"
1960                 else:
1961                     automode = "MANUAL"
1962                     key = scanner.nexttok()
1963             elif scanner.sees("automatic"):
1964                 if (not itarg) and len(game.enemies) != 0:
1965                     automode = "FORCEMAN"
1966                 else:
1967                     if len(game.enemies)==0:
1968                         prout(_("Energy will be expended into space."))
1969                     automode = "AUTOMATIC"
1970                     key = scanner.nexttok()
1971             elif scanner.sees("no"):
1972                 no = True
1973             else:
1974                 huh()
1975                 return
1976         elif key == "IHREAL":
1977             if len(game.enemies)==0:
1978                 prout(_("Energy will be expended into space."))
1979                 automode = "AUTOMATIC"
1980             elif not itarg:
1981                 automode = "FORCEMAN"
1982             else:
1983                 automode = "AUTOMATIC"
1984         else:
1985             # "IHEOL"
1986             if len(game.enemies)==0:
1987                 prout(_("Energy will be expended into space."))
1988                 automode = "AUTOMATIC"
1989             elif not itarg:
1990                 automode = "FORCEMAN"
1991             else:
1992                 proutn(_("Manual or automatic? "))
1993                 scanner.chew()
1994     avail = game.energy
1995     if ifast:
1996         avail -= 200.0
1997     if automode == "AUTOMATIC":
1998         if key == "IHALPHA" and scanner.sees("no"):
1999             no = True
2000             key = scanner.nexttok()
2001         if key != "IHREAL" and len(game.enemies) != 0:
2002             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
2003         irec = 0
2004         while True:
2005             scanner.chew()
2006             if not kz:
2007                 for i in range(len(game.enemies)):
2008                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*rnd.real(1.01, 1.06) + 1.0
2009             kz = 1
2010             proutn(_("%d units required. ") % irec)
2011             scanner.chew()
2012             proutn(_("Units to fire= "))
2013             key = scanner.nexttok()
2014             if key != "IHREAL":
2015                 return
2016             rpow = scanner.real
2017             if rpow > avail:
2018                 proutn(_("Energy available= %.2f") % avail)
2019                 skip(1)
2020                 key = "IHEOL"
2021             if not rpow > avail:
2022                 break
2023         if rpow <= 0:
2024             # chicken out
2025             scanner.chew()
2026             return
2027         key = scanner.nexttok()
2028         if key == "IHALPHA" and scanner.sees("no"):
2029             no = True
2030         if ifast:
2031             game.energy -= 200 # Go and do it!
2032             if checkshctrl(rpow):
2033                 return
2034         scanner.chew()
2035         game.energy -= rpow
2036         extra = rpow
2037         if len(game.enemies):
2038             extra = 0.0
2039             powrem = rpow
2040             for i in range(len(game.enemies)):
2041                 hits.append(0.0)
2042                 if powrem <= 0:
2043                     continue
2044                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
2045                 over = rnd.real(1.01, 1.06) * hits[i]
2046                 temp = powrem
2047                 powrem -= hits[i] + over
2048                 if powrem <= 0 and temp < hits[i]:
2049                     hits[i] = temp
2050                 if powrem <= 0:
2051                     over = 0.0
2052                 extra += over
2053             if powrem > 0.0:
2054                 extra += powrem
2055             hittem(hits)
2056             game.ididit = True
2057         if extra > 0 and not game.alldone:
2058             if game.tholian:
2059                 proutn(_("*** Tholian web absorbs "))
2060                 if len(game.enemies)>0:
2061                     proutn(_("excess "))
2062                 prout(_("phaser energy."))
2063             else:
2064                 prout(_("%d expended on empty space.") % int(extra))
2065     elif automode == "FORCEMAN":
2066         scanner.chew()
2067         key = "IHEOL"
2068         if damaged(DCOMPTR):
2069             prout(_("Battle computer damaged, manual fire only."))
2070         else:
2071             skip(1)
2072             prouts(_("---WORKING---"))
2073             skip(1)
2074             prout(_("Short-range-sensors-damaged"))
2075             prout(_("Insufficient-data-for-automatic-phaser-fire"))
2076             prout(_("Manual-fire-must-be-used"))
2077             skip(1)
2078     elif automode == "MANUAL":
2079         rpow = 0.0
2080         for k in range(len(game.enemies)):
2081             aim = game.enemies[k].location
2082             ienm = game.quad[aim.i][aim.j]
2083             if msgflag:
2084                 proutn(_("Energy available= %.2f") % (avail-0.006))
2085                 skip(1)
2086                 msgflag = False
2087                 rpow = 0.0
2088             if damaged(DSRSENS) and \
2089                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
2090                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2091                 scanner.chew()
2092                 key = "IHEOL"
2093                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
2094                 continue
2095             if key == "IHEOL":
2096                 scanner.chew()
2097                 if itarg and k > kz:
2098                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        rnd.real(1.01, 1.06) + 1.0
2099                 kz = k
2100                 proutn("(")
2101                 if not damaged(DCOMPTR):
2102                     proutn("%d" % irec)
2103                 else:
2104                     proutn("??")
2105                 proutn(")  ")
2106                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
2107                 key = scanner.nexttok()
2108             if key == "IHALPHA" and scanner.sees("no"):
2109                 no = True
2110                 key = scanner.nexttok()
2111                 continue
2112             if key == "IHALPHA":
2113                 huh()
2114                 return
2115             if key == "IHEOL":
2116                 if k == 1: # Let me say I'm baffled by this
2117                     msgflag = True
2118                 continue
2119             if scanner.real < 0:
2120                 # abort out
2121                 scanner.chew()
2122                 return
2123             hits.append(scanner.real)
2124             rpow += scanner.real
2125             # If total requested is too much, inform and start over
2126             if rpow > avail:
2127                 prout(_("Available energy exceeded -- try again."))
2128                 scanner.chew()
2129                 return
2130             key = scanner.nexttok() # scan for next value
2131         if rpow == 0.0:
2132             # zero energy -- abort
2133             scanner.chew()
2134             return
2135         if key == "IHALPHA" and scanner.sees("no"):
2136             no = True
2137         game.energy -= rpow
2138         scanner.chew()
2139         if ifast:
2140             game.energy -= 200.0
2141             if checkshctrl(rpow):
2142                 return
2143         hittem(hits)
2144         game.ididit = True
2145      # Say shield raised or malfunction, if necessary
2146     if game.alldone:
2147         return
2148     if ifast:
2149         skip(1)
2150         if no == 0:
2151             if rnd.withprob(0.01):
2152                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2153                 prouts(_("         CLICK   CLICK   POP  . . ."))
2154                 prout(_(" No response, sir!"))
2155                 game.shldup = False
2156             else:
2157                 prout(_("Shields raised."))
2158         else:
2159             game.shldup = False
2160     overheat(rpow)
2161
2162
2163 def capture():
2164     game.ididit = False # Nothing if we fail
2165     game.optime = 0.0
2166
2167     # Make sure there is room in the brig
2168     if game.brigfree == 0:
2169         prout(_("Security reports the brig is already full."))
2170         return
2171
2172     if damaged(DRADIO):
2173         prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
2174         return
2175
2176     if damaged(DTRANSP):
2177         prout(_("Scotty- \"Transporter damaged, sir.\""))
2178         return
2179
2180     # find out if there are any at all
2181     if game.klhere < 1:
2182         prout(_("Uhura- \"Getting no response, sir.\""))
2183         return
2184
2185     # if there is more than one Klingon, find out which one
2186     #   Cruddy, just takes one at random.  Should ask the captain.
2187     #   Nah, just select the weakest one since it is most likely to
2188     #   surrender (Tom Almy mod)
2189     klingons = [e for e in game.enemies if e.type == 'K']
2190     weakest = sorted(klingons, key=lambda e: e.power)[0]
2191     game.optime = 0.05          # This action will take some time
2192     game.ididit = True #  So any others can strike back
2193
2194     # check out that Klingon
2195     # The algorithm isn't that great and could use some more
2196     # intelligent design
2197     # x = 300 + 25*skill;
2198     x = game.energy / (weakest.power * len(klingons))
2199     #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
2200     #      % (game.energy, weakest.power, len(klingons)))
2201     x *= 2.5    # would originally have been equivalent of 1.4,
2202                 # but we want command to work more often, more humanely
2203     #prout(_("Prob = %.4f" % x))
2204     #   x = 100; // For testing, of course!
2205     if x < rnd.real(100):
2206         # guess what, he surrendered!!!
2207         prout(_("Klingon captain at %s surrenders.") % weakest.location)
2208         i = rnd.real(200)
2209         if i > 0:
2210             prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
2211         if i > game.brigfree:
2212             prout(_("%d Klingons die because there is no room for them in the brig.") % (i-game.brigfree))
2213             i = game.brigfree
2214         game.brigfree -= i
2215         prout(_("%d captives taken") % i)
2216         deadkl(weakest.location, weakest.type, game.sector)
2217         if game.unwon()<=0:
2218             finish(FWON)
2219         return
2220
2221         # big surprise, he refuses to surrender
2222     prout(_("Fat chance, captain!"))
2223
2224 # Code from events.c begins here.
2225
2226 # This isn't a real event queue a la BSD Trek yet -- you can only have one
2227 # event of each type active at any given time.  Mostly these means we can
2228 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2229 # BSD Trek, from which we swiped the idea, can have up to 5.
2230
2231 def unschedule(evtype):
2232     "Remove an event from the schedule."
2233     game.future[evtype].date = FOREVER
2234     return game.future[evtype]
2235
2236 def is_scheduled(evtype):
2237     "Is an event of specified type scheduled."
2238     return game.future[evtype].date != FOREVER
2239
2240 def scheduled(evtype):
2241     "When will this event happen?"
2242     return game.future[evtype].date
2243
2244 def schedule(evtype, offset):
2245     "Schedule an event of specified type."
2246     game.future[evtype].date = game.state.date + offset
2247     return game.future[evtype]
2248
2249 def postpone(evtype, offset):
2250     "Postpone a scheduled event."
2251     game.future[evtype].date += offset
2252
2253 def cancelrest():
2254     "Rest period is interrupted by event."
2255     if game.resting:
2256         skip(1)
2257         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2258         if ja():
2259             game.resting = False
2260             game.optime = 0.0
2261             return True
2262     return False
2263
2264 def events():
2265     "Run through the event queue looking for things to do."
2266     i = 0
2267     fintim = game.state.date + game.optime
2268     yank = 0
2269     ictbeam = False
2270     istract = False
2271     w = Coord()
2272     hold = Coord()
2273     ev = Event()
2274     ev2 = Event()
2275
2276     def tractorbeam(yank):
2277         "Tractor-beaming cases merge here."
2278         announce()
2279         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2280         skip(1)
2281         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2282         # If Kirk & Co. screwing around on planet, handle
2283         atover(True) # atover(true) is Grab
2284         if game.alldone:
2285             return
2286         if game.icraft: # Caught in Galileo?
2287             finish(FSTRACTOR)
2288             return
2289         # Check to see if shuttle is aboard
2290         if game.iscraft == "offship":
2291             skip(1)
2292             if rnd.withprob(0.5):
2293                 prout(_("Galileo, left on the planet surface, is captured"))
2294                 prout(_("by aliens and made into a flying McDonald's."))
2295                 game.damage[DSHUTTL] = -10
2296                 game.iscraft = "removed"
2297             else:
2298                 prout(_("Galileo, left on the planet surface, is well hidden."))
2299         if evcode == FSPY:
2300             game.quadrant = game.state.kscmdr
2301         else:
2302             game.quadrant = game.state.kcmdr[i]
2303         game.sector = randplace(QUADSIZE)
2304         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2305                % (game.quadrant, game.sector))
2306         if game.resting:
2307             prout(_("(Remainder of rest/repair period cancelled.)"))
2308             game.resting = False
2309         if not game.shldup:
2310             if not damaged(DSHIELD) and game.shield > 0:
2311                 doshield(shraise=True) # raise shields
2312                 game.shldchg = False
2313             else:
2314                 prout(_("(Shields not currently useable.)"))
2315         newqad()
2316         # Adjust finish time to time of tractor beaming?
2317         # fintim = game.state.date+game.optime
2318         attack(torps_ok=False)
2319         if not game.state.kcmdr:
2320             unschedule(FTBEAM)
2321         else:
2322             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2323
2324     def destroybase():
2325         "Code merges here for any commander destroying a starbase."
2326         # Not perfect, but will have to do
2327         # Handle case where base is in same quadrant as starship
2328         if game.battle == game.quadrant:
2329             game.state.chart[game.battle.i][game.battle.j].starbase = False
2330             game.quad[game.base.i][game.base.j] = '.'
2331             game.base.invalidate()
2332             newcnd()
2333             skip(1)
2334             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2335         elif game.state.baseq and communicating():
2336             # Get word via subspace radio
2337             announce()
2338             skip(1)
2339             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2340             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2341             if game.isatb == 2:
2342                 prout(_("the Klingon Super-Commander"))
2343             else:
2344                 prout(_("a Klingon Commander"))
2345             game.state.chart[game.battle.i][game.battle.j].starbase = False
2346         # Remove Starbase from galaxy
2347         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2348         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2349         if game.isatb == 2:
2350             # reinstate a commander's base attack
2351             game.battle = hold
2352             game.isatb = 0
2353         else:
2354             game.battle.invalidate()
2355     if game.idebug:
2356         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2357         for i in range(1, NEVENTS):
2358             if   i == FSNOVA:  proutn("=== Supernova       ")
2359             elif i == FTBEAM:  proutn("=== T Beam          ")
2360             elif i == FSNAP:   proutn("=== Snapshot        ")
2361             elif i == FBATTAK: proutn("=== Base Attack     ")
2362             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2363             elif i == FSCMOVE: proutn("=== SC Move         ")
2364             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2365             elif i == FDSPROB: proutn("=== Probe Move      ")
2366             elif i == FDISTR:  proutn("=== Distress Call   ")
2367             elif i == FENSLV:  proutn("=== Enslavement     ")
2368             elif i == FREPRO:  proutn("=== Klingon Build   ")
2369             if is_scheduled(i):
2370                 prout("%.2f" % (scheduled(i)))
2371             else:
2372                 prout("never")
2373     radio_was_broken = damaged(DRADIO)
2374     hold.i = hold.j = 0
2375     while True:
2376         # Select earliest extraneous event, evcode==0 if no events
2377         evcode = FSPY
2378         if game.alldone:
2379             return
2380         datemin = fintim
2381         for l in range(1, NEVENTS):
2382             if game.future[l].date < datemin:
2383                 evcode = l
2384                 if game.idebug:
2385                     prout("== Event %d fires" % evcode)
2386                 datemin = game.future[l].date
2387         xtime = datemin-game.state.date
2388         if game.iscloaked:
2389             game.energy -= xtime*500.0
2390             if game.energy <= 0:
2391                 finish(FNRG)
2392                 return
2393         game.state.date = datemin
2394         # Decrement Federation resources and recompute remaining time
2395         game.state.remres -= (game.remkl()+4*len(game.state.kcmdr))*xtime
2396         game.recompute()
2397         if game.state.remtime <= 0:
2398             finish(FDEPLETE)
2399             return
2400         # Any crew left alive?
2401         if game.state.crew <= 0:
2402             finish(FCREW)
2403             return
2404         # Is life support adequate?
2405         if damaged(DLIFSUP) and game.condition != "docked":
2406             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2407                 finish(FLIFESUP)
2408                 return
2409             game.lsupres -= xtime
2410             if game.damage[DLIFSUP] <= xtime:
2411                 game.lsupres = game.inlsr
2412         # Fix devices
2413         repair = xtime
2414         if game.condition == "docked":
2415             repair /= DOCKFAC
2416         # Don't fix Deathray here
2417         for l in range(NDEVICES):
2418             if game.damage[l] > 0.0 and l != DDRAY:
2419                 if game.damage[l]-repair > 0.0:
2420                     game.damage[l] -= repair
2421                 else:
2422                     game.damage[l] = 0.0
2423         # If radio repaired, update star chart and attack reports
2424         if radio_was_broken and not damaged(DRADIO):
2425             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2426             prout(_("   surveillance reports are coming in."))
2427             skip(1)
2428             if not game.iseenit:
2429                 attackreport(False)
2430                 game.iseenit = True
2431             rechart()
2432             prout(_("   The star chart is now up to date.\""))
2433             skip(1)
2434         # Cause extraneous event EVCODE to occur
2435         game.optime -= xtime
2436         if evcode == FSNOVA: # Supernova
2437             announce()
2438             supernova(None)
2439             schedule(FSNOVA, expran(0.5*game.intime))
2440             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2441                 return
2442         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2443             if game.state.nscrem == 0 or game.iscloaked or \
2444                 ictbeam or istract or \
2445                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2446                 return
2447             if game.ientesc or \
2448                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2449                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2450                 (damaged(DSHIELD) and \
2451                  (game.energy < 2500 or damaged(DPHASER)) and \
2452                  (game.torps < 5 or damaged(DPHOTON))):
2453                 # Tractor-beam her!
2454                 istract = ictbeam = True
2455                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2456             else:
2457                 return
2458         elif evcode == FTBEAM: # Tractor beam
2459             if not game.state.kcmdr:
2460                 unschedule(FTBEAM)
2461                 continue
2462             i = rnd.integer(len(game.state.kcmdr))
2463             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2464             if istract or game.condition == "docked" or game.iscloaked or yank == 0:
2465                 # Drats! Have to reschedule
2466                 schedule(FTBEAM,
2467                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2468                 continue
2469             ictbeam = True
2470             tractorbeam(yank)
2471         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2472             game.snapsht = copy.deepcopy(game.state)
2473             game.state.snap = True
2474             schedule(FSNAP, expran(0.5 * game.intime))
2475         elif evcode == FBATTAK: # Commander attacks starbase
2476             if not game.state.kcmdr or not game.state.baseq:
2477                 # no can do
2478                 unschedule(FBATTAK)
2479                 unschedule(FCDBAS)
2480                 continue
2481             ibq = None  # Force battle location to persist past loop
2482             try:
2483                 for ibq in game.state.baseq:
2484                     for cmdr in game.state.kcmdr:
2485                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2486                             raise JumpOut
2487                 # no match found -- try later
2488                 schedule(FBATTAK, expran(0.3*game.intime))
2489                 unschedule(FCDBAS)
2490                 continue
2491             except JumpOut:
2492                 pass
2493             # commander + starbase combination found -- launch attack
2494             game.battle = ibq
2495             schedule(FCDBAS, rnd.real(1.0, 4.0))
2496             if game.isatb: # extra time if SC already attacking
2497                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2498             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2499             game.iseenit = False
2500             if not communicating():
2501                 continue # No warning :-(
2502             game.iseenit = True
2503             announce()
2504             skip(1)
2505             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2506             prout(_("   reports that it is under attack and that it can"))
2507             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2508             if cancelrest():
2509                 return
2510         elif evcode == FSCDBAS: # Supercommander destroys base
2511             unschedule(FSCDBAS)
2512             game.isatb = 2
2513             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2514                 continue # WAS RETURN!
2515             hold = game.battle
2516             game.battle = game.state.kscmdr
2517             destroybase()
2518         elif evcode == FCDBAS: # Commander succeeds in destroying base
2519             if evcode == FCDBAS:
2520                 unschedule(FCDBAS)
2521                 if not game.state.baseq \
2522                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2523                     game.battle.invalidate()
2524                     continue
2525                 # find the lucky pair
2526                 for cmdr in game.state.kcmdr:
2527                     if cmdr == game.battle:
2528                         break
2529                 else:
2530                     # No action to take after all
2531                     continue
2532             destroybase()
2533         elif evcode == FSCMOVE: # Supercommander moves
2534             schedule(FSCMOVE, 0.2777)
2535             if not game.ientesc and not istract and game.isatb != 1 and \
2536                    (not game.iscate or not game.justin):
2537                 supercommander()
2538         elif evcode == FDSPROB: # Move deep space probe
2539             schedule(FDSPROB, 0.01)
2540             if not game.probe.nexttok():
2541                 if not game.probe.quadrant().valid_quadrant() or \
2542                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2543                     # Left galaxy or ran into supernova
2544                     if communicating():
2545                         announce()
2546                         skip(1)
2547                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2548                         if not game.probe.quadrant().valid_quadrant():
2549                             prout(_("has left the galaxy.\""))
2550                         else:
2551                             prout(_("is no longer transmitting.\""))
2552                     unschedule(FDSPROB)
2553                     continue
2554                 if communicating():
2555                     #announce()
2556                     skip(1)
2557                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2558             pquad = game.probe.quadrant()
2559             pdest = game.state.galaxy[pquad.i][pquad.j]
2560             if communicating():
2561                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2562                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2563                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2564                 pdest.charted = True
2565             game.probe.moves -= 1 # One less to travel
2566             if game.probe.arrived() and game.isarmed and pdest.stars:
2567                 supernova(game.probe.quadrant())                # fire in the hole!
2568                 unschedule(FDSPROB)
2569                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2570                     return
2571         elif evcode == FDISTR: # inhabited system issues distress call
2572             unschedule(FDISTR)
2573             # try a whole bunch of times to find something suitable
2574             for i in range(100):
2575                 # need a quadrant which is not the current one,
2576                 # which has some stars which are inhabited and
2577                 # not already under attack, which is not
2578                 # supernova'ed, and which has some Klingons in it
2579                 w = randplace(GALSIZE)
2580                 q = game.state.galaxy[w.i][w.j]
2581                 if not (game.quadrant == w or q.planet is None or \
2582                       not q.planet.inhabited or \
2583                       q.supernova or q.status!="secure" or q.klingons<=0):
2584                     break
2585             else:
2586                 # can't seem to find one; ignore this call
2587                 if game.idebug:
2588                     prout("=== Couldn't find location for distress event.")
2589                 continue
2590             # got one!!  Schedule its enslavement
2591             ev = schedule(FENSLV, expran(game.intime))
2592             ev.quadrant = w
2593             q.status = "distressed"
2594             # tell the captain about it if we can
2595             if communicating():
2596                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2597                         % (q.planet, repr(w)))
2598                 prout(_("by a Klingon invasion fleet."))
2599                 if cancelrest():
2600                     return
2601         elif evcode == FENSLV:                # starsystem is enslaved
2602             ev = unschedule(FENSLV)
2603             # see if current distress call still active
2604             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2605             if q.klingons <= 0:
2606                 q.status = "secure"
2607                 continue
2608             q.status = "enslaved"
2609
2610             # play stork and schedule the first baby
2611             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2612             ev2.quadrant = ev.quadrant
2613
2614             # report the disaster if we can
2615             if communicating():
2616                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2617                         q.planet)
2618                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2619         elif evcode == FREPRO:                # Klingon reproduces
2620             # If we ever switch to a real event queue, we'll need to
2621             # explicitly retrieve and restore the x and y.
2622             ev = schedule(FREPRO, expran(1.0 * game.intime))
2623             # see if current distress call still active
2624             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2625             if q.klingons <= 0:
2626                 q.status = "secure"
2627                 continue
2628             if game.remkl() >= MAXKLGAME:
2629                 continue                # full right now
2630             # reproduce one Klingon
2631             w = ev.quadrant
2632             m = Coord()
2633             if game.klhere >= MAXKLQUAD:
2634                 try:
2635                     # this quadrant not ok, pick an adjacent one
2636                     for m.i in range(w.i - 1, w.i + 2):
2637                         for m.j in range(w.j - 1, w.j + 2):
2638                             if not m.valid_quadrant():
2639                                 continue
2640                             q = game.state.galaxy[m.i][m.j]
2641                             # check for this quad ok (not full & no snova)
2642                             if q.klingons >= MAXKLQUAD or q.supernova:
2643                                 continue
2644                             raise JumpOut
2645                     # search for eligible quadrant failed
2646                     continue
2647                 except JumpOut:
2648                     w = m
2649             # deliver the child
2650             q.klingons += 1
2651             if game.quadrant == w:
2652                 game.klhere += 1
2653                 newkling() # also adds it to game.enemies
2654             # recompute time left
2655             game.recompute()
2656             if communicating():
2657                 if game.quadrant == w:
2658                     prout(_("Spock- sensors indicate the Klingons have"))
2659                     prout(_("launched a warship from %s.") % q.planet)
2660                 else:
2661                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2662                     if q.planet is not None:
2663                         proutn(_("near %s ") % q.planet)
2664                     prout(_("in Quadrant %s.") % w)
2665
2666 def wait():
2667     "Wait on events."
2668     game.ididit = False
2669     while True:
2670         key = scanner.nexttok()
2671         if key  != "IHEOL":
2672             break
2673         proutn(_("How long? "))
2674         scanner.chew()
2675     if key != "IHREAL":
2676         huh()
2677         return
2678     origTime = delay = scanner.real
2679     if delay <= 0.0:
2680         return
2681     if delay >= game.state.remtime or len(game.enemies) != 0:
2682         proutn(_("Are you sure? "))
2683         if not ja():
2684             return
2685     # Alternate resting periods (events) with attacks
2686     game.resting = True
2687     while True:
2688         if delay <= 0:
2689             game.resting = False
2690         if not game.resting:
2691             prout(_("%d stardates left.") % int(game.state.remtime))
2692             return
2693         temp = game.optime = delay
2694         if len(game.enemies):
2695             rtime = rnd.real(1.0, 2.0)
2696             if rtime < temp:
2697                 temp = rtime
2698             game.optime = temp
2699         if game.optime < delay:
2700             attack(torps_ok=False)
2701         if game.alldone:
2702             return
2703         events()
2704         game.ididit = True
2705         if game.alldone:
2706             return
2707         delay -= temp
2708         # Repair Deathray if long rest at starbase
2709         if origTime-delay >= 9.99 and game.condition == "docked":
2710             game.damage[DDRAY] = 0.0
2711         # leave if quadrant supernovas
2712         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2713             break
2714     game.resting = False
2715     game.optime = 0.0
2716
2717 def nova(nov):
2718     "Star goes nova."
2719     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2720     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2721     if rnd.withprob(0.05):
2722         # Wow! We've supernova'ed
2723         supernova(game.quadrant)
2724         return
2725     # handle initial nova
2726     game.quad[nov.i][nov.j] = '.'
2727     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2728     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2729     game.state.starkl += 1
2730     # Set up queue to recursively trigger adjacent stars
2731     hits = [nov]
2732     kount = 0
2733     while hits:
2734         offset = Coord()
2735         start = hits.pop()
2736         for offset.i in range(-1, 1+1):
2737             for offset.j in range(-1, 1+1):
2738                 if offset.j == 0 and offset.i == 0:
2739                     continue
2740                 neighbor = start + offset
2741                 if not neighbor.valid_sector():
2742                     continue
2743                 iquad = game.quad[neighbor.i][neighbor.j]
2744                 # Empty space ends reaction
2745                 if iquad in ('.', '?', ' ', 'T', '#'):
2746                     pass
2747                 elif iquad == '*': # Affect another star
2748                     if rnd.withprob(0.05):
2749                         # This star supernovas
2750                         supernova(game.quadrant)
2751                         return
2752                     else:
2753                         hits.append(neighbor)
2754                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2755                         game.state.starkl += 1
2756                         proutn(crmena(True, '*', "sector", neighbor))
2757                         prout(_(" novas."))
2758                         game.quad[neighbor.i][neighbor.j] = '.'
2759                         kount += 1
2760                 elif iquad in ('P', '@'): # Destroy planet
2761                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2762                     if iquad == 'P':
2763                         game.state.nplankl += 1
2764                     else:
2765                         game.state.nworldkl += 1
2766                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2767                     game.iplnet.pclass = "destroyed"
2768                     game.iplnet = None
2769                     game.plnet.invalidate()
2770                     if game.landed:
2771                         finish(FPNOVA)
2772                         return
2773                     game.quad[neighbor.i][neighbor.j] = '.'
2774                 elif iquad == 'B': # Destroy base
2775                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2776                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2777                     game.base.invalidate()
2778                     game.state.basekl += 1
2779                     newcnd()
2780                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2781                     game.quad[neighbor.i][neighbor.j] = '.'
2782                 elif iquad in ('E', 'F'): # Buffet ship
2783                     prout(_("***Starship buffeted by nova."))
2784                     if game.shldup:
2785                         if game.shield >= 2000.0:
2786                             game.shield -= 2000.0
2787                         else:
2788                             diff = 2000.0 - game.shield
2789                             game.energy -= diff
2790                             game.shield = 0.0
2791                             game.shldup = False
2792                             prout(_("***Shields knocked out."))
2793                             game.damage[DSHIELD] += 0.005*game.damfac*rnd.real()*diff
2794                     else:
2795                         game.energy -= 2000.0
2796                     if game.energy <= 0:
2797                         finish(FNOVA)
2798                         return
2799                     # add in course nova contributes to kicking starship
2800                     if hits:
2801                         bump += (game.sector-hits[-1]).sgn()
2802                 elif iquad == 'K': # kill klingon
2803                     deadkl(neighbor, iquad, neighbor)
2804                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2805                     target = None
2806                     for ll in range(len(game.enemies)):
2807                         if game.enemies[ll].location == neighbor:
2808                             target = game.enemies[ll]
2809                             break
2810                     if target is not None:
2811                         target.power -= 800.0 # If firepower is lost, die
2812                         if target.power <= 0.0:
2813                             deadkl(neighbor, iquad, neighbor)
2814                             continue    # neighbor loop
2815                         # Else enemy gets flung by the blast wave
2816                         newc = neighbor + neighbor - start
2817                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2818                         if not newc.valid_sector():
2819                             # can't leave quadrant
2820                             skip(1)
2821                             continue
2822                         iquad1 = game.quad[newc.i][newc.j]
2823                         if iquad1 == ' ':
2824                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2825                             skip(1)
2826                             deadkl(neighbor, iquad, newc)
2827                             continue
2828                         if iquad1 != '.':
2829                             # can't move into something else
2830                             skip(1)
2831                             continue
2832                         proutn(_(", buffeted to Sector %s") % newc)
2833                         game.quad[neighbor.i][neighbor.j] = '.'
2834                         game.quad[newc.i][newc.j] = iquad
2835                         target.move(newc)
2836     # Starship affected by nova -- kick it away.
2837     dist = kount*0.1
2838     direc = ncourse[3*(bump.i+1)+bump.j+2]
2839     if direc == 0.0:
2840         dist = 0.0
2841     if dist == 0.0:
2842         return
2843     scourse = course(bearing=direc, distance=dist)
2844     game.optime = scourse.time(w=4)
2845     skip(1)
2846     prout(_("Force of nova displaces starship."))
2847     imove(scourse, noattack=True)
2848     game.optime = scourse.time(w=4)
2849     return
2850
2851 def supernova(w):
2852     "Star goes supernova."
2853     num = 0; npdead = 0
2854     if w is not None:
2855         nq = copy.copy(w)
2856     else:
2857         # Scheduled supernova -- select star at random.
2858         nstars = 0
2859         nq = Coord()
2860         for nq.i in range(GALSIZE):
2861             for nq.j in range(GALSIZE):
2862                 nstars += game.state.galaxy[nq.i][nq.j].stars
2863         if nstars == 0:
2864             return # nothing to supernova exists
2865         num = rnd.integer(nstars) + 1
2866         for nq.i in range(GALSIZE):
2867             for nq.j in range(GALSIZE):
2868                 num -= game.state.galaxy[nq.i][nq.j].stars
2869                 if num <= 0:
2870                     break
2871             if num <=0:
2872                 break
2873         if game.idebug:
2874             proutn("=== Super nova here?")
2875             if ja():
2876                 nq = game.quadrant
2877     if nq != game.quadrant or game.justin:
2878         # it isn't here, or we just entered (treat as enroute)
2879         if communicating():
2880             skip(1)
2881             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2882             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2883     else:
2884         ns = Coord()
2885         # we are in the quadrant!
2886         num = rnd.integer(game.state.galaxy[nq.i][nq.j].stars) + 1
2887         for ns.i in range(QUADSIZE):
2888             for ns.j in range(QUADSIZE):
2889                 if game.quad[ns.i][ns.j]=='*':
2890                     num -= 1
2891                     if num==0:
2892                         break
2893             if num==0:
2894                 break
2895         skip(1)
2896         prouts(_("***RED ALERT!  RED ALERT!"))
2897         skip(1)
2898         prout(_("***Incipient supernova detected at Sector %s") % ns)
2899         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2900             proutn(_("Emergency override attempts t"))
2901             prouts("***************")
2902             skip(1)
2903             stars()
2904             game.alldone = True
2905     # destroy any Klingons in supernovaed quadrant
2906     game.state.galaxy[nq.i][nq.j].klingons = 0
2907     if nq == game.state.kscmdr:
2908         # did in the Supercommander!
2909         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2910         game.iscate = False
2911         unschedule(FSCMOVE)
2912         unschedule(FSCDBAS)
2913     # Changing this to [w for w in game.state.kcmdr if w != nq]
2914     # causes regression-test failure
2915     survivors = list(filter(lambda w: w != nq, game.state.kcmdr))
2916     #comkills = len(game.state.kcmdr) - len(survivors)
2917     game.state.kcmdr = survivors
2918     if not game.state.kcmdr:
2919         unschedule(FTBEAM)
2920     # destroy Romulans and planets in supernovaed quadrant
2921     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2922     game.state.galaxy[nq.i][nq.j].romulans = 0
2923     game.state.nromrem -= nrmdead
2924     # Destroy planets
2925     for loop in range(game.inplan):
2926         if game.state.planets[loop].quadrant == nq:
2927             game.state.planets[loop].pclass = "destroyed"
2928             npdead += 1
2929     # Destroy any base in supernovaed quadrant
2930     game.state.baseq = [x for x in game.state.baseq if x != nq]
2931     # If starship caused supernova, tally up destruction
2932     if w is not None:
2933         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2934         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2935         game.state.nplankl += npdead
2936     # mark supernova in galaxy and in star chart
2937     if game.quadrant == nq or communicating():
2938         game.state.galaxy[nq.i][nq.j].supernova = True
2939     # If supernova destroys last Klingons give special message
2940     if game.unwon()==0 and nq != game.quadrant:
2941         skip(2)
2942         if w is None:
2943             prout(_("Lucky you!"))
2944         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2945         finish(FWON)
2946         return
2947     # if some Klingons remain, continue or die in supernova
2948     if game.alldone:
2949         finish(FSNOVAED)
2950     return
2951
2952 # Code from finish.c ends here.
2953
2954 def selfdestruct():
2955     "Self-destruct maneuver. Finish with a BANG!"
2956     scanner.chew()
2957     if damaged(DCOMPTR):
2958         prout(_("Computer damaged; cannot execute destruct sequence."))
2959         return
2960     prouts(_("---WORKING---")); skip(1)
2961     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2962     prouts("   10"); skip(1)
2963     prouts("       9"); skip(1)
2964     prouts("          8"); skip(1)
2965     prouts("             7"); skip(1)
2966     prouts("                6"); skip(1)
2967     skip(1)
2968     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2969     skip(1)
2970     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2971     skip(1)
2972     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2973     skip(1)
2974     scanner.nexttok()
2975     if game.passwd != scanner.token:
2976         prouts(_("PASSWORD-REJECTED;"))
2977         skip(1)
2978         prouts(_("CONTINUITY-EFFECTED"))
2979         skip(2)
2980         return
2981     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2982     prouts("                   5"); skip(1)
2983     prouts("                      4"); skip(1)
2984     prouts("                         3"); skip(1)
2985     prouts("                            2"); skip(1)
2986     prouts("                              1"); skip(1)
2987     if rnd.withprob(0.15):
2988         prouts(_("GOODBYE-CRUEL-WORLD"))
2989         skip(1)
2990     kaboom()
2991
2992 def kaboom():
2993     stars()
2994     if game.ship=='E':
2995         prouts("***")
2996     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2997     skip(1)
2998     stars()
2999     skip(1)
3000     if len(game.enemies) != 0:
3001         whammo = 25.0 * game.energy
3002         for e in game.enemies[::-1]:
3003             if e.power*e.kdist <= whammo:
3004                 deadkl(e.location, game.quad[e.location.i][e.location.j], e.location)
3005     finish(FDILITHIUM)
3006
3007 def killrate():
3008     "Compute our rate of kils over time."
3009     elapsed = game.state.date - game.indate
3010     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
3011         return 0
3012     else:
3013         starting = (game.inkling + game.incom + game.inscom)
3014         remaining = game.unwon()
3015         return (starting - remaining)/elapsed
3016
3017 def badpoints():
3018     "Compute demerits."
3019     badpt = 5.0*game.state.starkl + \
3020             game.casual + \
3021             10.0*game.state.nplankl + \
3022             300*game.state.nworldkl + \
3023             45.0*game.nhelp +\
3024             100.0*game.state.basekl +\
3025             3.0*game.abandoned +\
3026             100*game.ncviol
3027     if game.ship == 'F':
3028         badpt += 100.0
3029     elif game.ship is None:
3030         badpt += 200.0
3031     return badpt
3032
3033 def finish(ifin):
3034     # end the game, with appropriate notifications
3035     igotit = False
3036     game.alldone = True
3037     skip(3)
3038     prout(_("It is stardate %.1f.") % game.state.date)
3039     skip(1)
3040     if ifin == FWON: # Game has been won
3041         if game.state.nromrem != 0:
3042             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
3043                   game.state.nromrem)
3044
3045         prout(_("You have smashed the Klingon invasion fleet and saved"))
3046         prout(_("the Federation."))
3047         if game.alive and game.brigcapacity-game.brigfree > 0:
3048             game.kcaptured += game.brigcapacity-game.brigfree
3049             prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
3050         game.gamewon = True
3051         if game.alive:
3052             badpt = badpoints()
3053             if badpt < 100.0:
3054                 badpt = 0.0        # Close enough!
3055             # killsPerDate >= RateMax
3056             if game.state.date-game.indate < 5.0 or \
3057                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
3058                 skip(1)
3059                 prout(_("In fact, you have done so well that Starfleet Command"))
3060                 if game.skill == SKILL_NOVICE:
3061                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
3062                 elif game.skill == SKILL_FAIR:
3063                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
3064                 elif game.skill == SKILL_GOOD:
3065                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
3066                 elif game.skill == SKILL_EXPERT:
3067                     prout(_("promotes you to Commodore Emeritus."))
3068                     skip(1)
3069                     prout(_("Now that you think you're really good, try playing"))
3070                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
3071                 elif game.skill == SKILL_EMERITUS:
3072                     skip(1)
3073                     proutn(_("Computer-  "))
3074                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
3075                     skip(2)
3076                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
3077                     skip(1)
3078                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3079                     skip(1)
3080                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3081                     skip(1)
3082                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
3083                     skip(1)
3084                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
3085                     skip(2)
3086                     prout(_("Now you can retire and write your own Star Trek game!"))
3087                     skip(1)
3088                 elif game.skill >= SKILL_EXPERT:
3089                     if game.thawed and not game.idebug:
3090                         prout(_("You cannot get a citation, so..."))
3091                     else:
3092                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3093                         scanner.chew()
3094                         if ja():
3095                             igotit = True
3096             # Only grant long life if alive (original didn't!)
3097             skip(1)
3098             prout(_("LIVE LONG AND PROSPER."))
3099         score()
3100         if igotit:
3101             plaque()
3102         return
3103     elif ifin == FDEPLETE: # Federation Resources Depleted
3104         prout(_("Your time has run out and the Federation has been"))
3105         prout(_("conquered.  Your starship is now Klingon property,"))
3106         prout(_("and you are put on trial as a war criminal.  On the"))
3107         proutn(_("basis of your record, you are "))
3108         if game.unwon()*3.0 > (game.inkling + game.incom + game.inscom):
3109             prout(_("acquitted."))
3110             skip(1)
3111             prout(_("LIVE LONG AND PROSPER."))
3112         else:
3113             prout(_("found guilty and"))
3114             prout(_("sentenced to death by slow torture."))
3115             game.alive = False
3116         score()
3117         return
3118     elif ifin == FLIFESUP:
3119         prout(_("Your life support reserves have run out, and"))
3120         prout(_("you die of thirst, starvation, and asphyxiation."))
3121         prout(_("Your starship is a derelict in space."))
3122     elif ifin == FNRG:
3123         prout(_("Your energy supply is exhausted."))
3124         skip(1)
3125         prout(_("Your starship is a derelict in space."))
3126     elif ifin == FBATTLE:
3127         prout(_("The %s has been destroyed in battle.") % crmshp())
3128         skip(1)
3129         prout(_("Dulce et decorum est pro patria mori."))
3130     elif ifin == FNEG3:
3131         prout(_("You have made three attempts to cross the negative energy"))
3132         prout(_("barrier which surrounds the galaxy."))
3133         skip(1)
3134         prout(_("Your navigation is abominable."))
3135         score()
3136     elif ifin == FNOVA:
3137         prout(_("Your starship has been destroyed by a nova."))
3138         prout(_("That was a great shot."))
3139         skip(1)
3140     elif ifin == FSNOVAED:
3141         prout(_("The %s has been fried by a supernova.") % crmshp())
3142         prout(_("...Not even cinders remain..."))
3143     elif ifin == FABANDN:
3144         prout(_("You have been captured by the Klingons. If you still"))
3145         prout(_("had a starbase to be returned to, you would have been"))
3146         prout(_("repatriated and given another chance. Since you have"))
3147         prout(_("no starbases, you will be mercilessly tortured to death."))
3148     elif ifin == FDILITHIUM:
3149         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3150     elif ifin == FMATERIALIZE:
3151         prout(_("Starbase was unable to re-materialize your starship."))
3152         prout(_("Sic transit gloria mundi"))
3153     elif ifin == FPHASER:
3154         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3155     elif ifin == FLOST:
3156         prout(_("You and your landing party have been"))
3157         prout(_("converted to energy, dissipating through space."))
3158     elif ifin == FMINING:
3159         prout(_("You are left with your landing party on"))
3160         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3161         skip(1)
3162         prout(_("They are very fond of \"Captain Kirk\" soup."))
3163         skip(1)
3164         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3165     elif ifin == FDPLANET:
3166         prout(_("You and your mining party perish."))
3167         skip(1)
3168         prout(_("That was a great shot."))
3169         skip(1)
3170     elif ifin == FSSC:
3171         prout(_("The Galileo is instantly annihilated by the supernova."))
3172         prout(_("You and your mining party are atomized."))
3173         skip(1)
3174         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3175         prout(_("joins the Romulans, wreaking terror on the Federation."))
3176     elif ifin == FPNOVA:
3177         prout(_("You and your mining party are atomized."))
3178         skip(1)
3179         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3180         prout(_("joins the Romulans, wreaking terror on the Federation."))
3181     elif ifin == FSTRACTOR:
3182         prout(_("The shuttle craft Galileo is also caught,"))
3183         prout(_("and breaks up under the strain."))
3184         skip(1)
3185         prout(_("Your debris is scattered for millions of miles."))
3186         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3187     elif ifin == FDRAY:
3188         prout(_("The mutants attack and kill Spock."))
3189         prout(_("Your ship is captured by Klingons, and"))
3190         prout(_("your crew is put on display in a Klingon zoo."))
3191     elif ifin == FTRIBBLE:
3192         prout(_("Tribbles consume all remaining water,"))
3193         prout(_("food, and oxygen on your ship."))
3194         skip(1)
3195         prout(_("You die of thirst, starvation, and asphyxiation."))
3196         prout(_("Your starship is a derelict in space."))
3197     elif ifin == FHOLE:
3198         prout(_("Your ship is drawn to the center of the black hole."))
3199         prout(_("You are crushed into extremely dense matter."))
3200     elif ifin == FCLOAK:
3201         game.ncviol += 1
3202         prout(_("You have violated the Treaty of Algeron."))
3203         prout(_("The Romulan Empire can never trust you again."))
3204     elif ifin == FCREW:
3205         prout(_("Your last crew member has died."))
3206     if ifin != FWON and ifin != FCLOAK and game.iscloaked:
3207         prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
3208         prout(_("You may have missed some warning messages."))
3209         skip(1)
3210     if game.ship == 'F':
3211         game.ship = None
3212     elif game.ship == 'E':
3213         game.ship = 'F'
3214     game.alive = False
3215     if game.unwon() != 0:
3216         goodies = game.state.remres/game.inresor
3217         baddies = (game.remkl() + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3218         if goodies/baddies >= rnd.real(1.0, 1.5):
3219             prout(_("As a result of your actions, a treaty with the Klingon"))
3220             prout(_("Empire has been signed. The terms of the treaty are"))
3221             if goodies/baddies >= rnd.real(3.0):
3222                 prout(_("favorable to the Federation."))
3223                 skip(1)
3224                 prout(_("Congratulations!"))
3225             else:
3226                 prout(_("highly unfavorable to the Federation."))
3227         else:
3228             prout(_("The Federation will be destroyed."))
3229     else:
3230         prout(_("Since you took the last Klingon with you, you are a"))
3231         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3232         prout(_("statue in your memory. Rest in peace, and try not"))
3233         prout(_("to think about pigeons."))
3234         game.gamewon = True
3235     score()
3236     scanner.chew()      # Clean up leftovers
3237
3238 def score():
3239     "Compute player's score."
3240     timused = game.state.date - game.indate
3241     if (timused == 0 or game.unwon() != 0) and timused < 5.0:
3242         timused = 5.0
3243     game.perdate = killrate()
3244     ithperd = 500*game.perdate + 0.5
3245     iwon = 0
3246     if game.gamewon:
3247         iwon = 100*game.skill
3248     if game.ship == 'E':
3249         klship = 0
3250     elif game.ship == 'F':
3251         klship = 1
3252     else:
3253         klship = 2
3254     dead_ordinaries= game.inkling - game.remkl() + len(game.state.kcmdr) + game.state.nscrem
3255     game.score = 10*(dead_ordinaries)\
3256              + 50*(game.incom - len(game.state.kcmdr)) \
3257              + ithperd + iwon \
3258              + 20*(game.inrom - game.state.nromrem) \
3259              + 200*(game.inscom - game.state.nscrem) \
3260                  - game.state.nromrem \
3261              + 3 * game.kcaptured \
3262              - badpoints()
3263     if not game.alive:
3264         game.score -= 200
3265     skip(2)
3266     prout(_("Your score --"))
3267     if game.inrom - game.state.nromrem:
3268         prout(_("%6d Romulans destroyed                 %5d") %
3269               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3270     if game.state.nromrem and game.gamewon:
3271         prout(_("%6d Romulans captured                  %5d") %
3272               (game.state.nromrem, game.state.nromrem))
3273     if dead_ordinaries:
3274         prout(_("%6d ordinary Klingons destroyed        %5d") %
3275               (dead_ordinaries, 10*dead_ordinaries))
3276     if game.incom - len(game.state.kcmdr):
3277         prout(_("%6d Klingon commanders destroyed       %5d") %
3278               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3279     if game.kcaptured:
3280         prout(_("%d Klingons captured                   %5d") %
3281               (game.kcaptured, 3 * game.kcaptured))
3282     if game.inscom - game.state.nscrem:
3283         prout(_("%6d Super-Commander destroyed          %5d") %
3284               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3285     if ithperd:
3286         prout(_("%6.2f Klingons per stardate              %5d") %
3287               (game.perdate, ithperd))
3288     if game.state.starkl:
3289         prout(_("%6d stars destroyed by your action     %5d") %
3290               (game.state.starkl, -5*game.state.starkl))
3291     if game.state.nplankl:
3292         prout(_("%6d planets destroyed by your action   %5d") %
3293               (game.state.nplankl, -10*game.state.nplankl))
3294     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3295         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3296               (game.state.nworldkl, -300*game.state.nworldkl))
3297     if game.state.basekl:
3298         prout(_("%6d bases destroyed by your action     %5d") %
3299               (game.state.basekl, -100*game.state.basekl))
3300     if game.nhelp:
3301         prout(_("%6d calls for help from starbase       %5d") %
3302               (game.nhelp, -45*game.nhelp))
3303     if game.casual:
3304         prout(_("%6d casualties incurred                %5d") %
3305               (game.casual, -game.casual))
3306     if game.abandoned:
3307         prout(_("%6d crew abandoned in space            %5d") %
3308               (game.abandoned, -3*game.abandoned))
3309     if klship:
3310         prout(_("%6d ship(s) lost or destroyed          %5d") %
3311               (klship, -100*klship))
3312     if game.ncviol > 0:
3313         if game.ncviol == 1:
3314             prout(_("1 Treaty of Algeron violation          -100"))
3315         else:
3316             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3317                   (game.ncviol, -100*game.ncviol))
3318     if not game.alive:
3319         prout(_("Penalty for getting yourself killed        -200"))
3320     if game.gamewon:
3321         proutn(_("Bonus for winning "))
3322         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3323         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3324         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3325         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3326         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3327         prout("           %5d" % iwon)
3328     skip(1)
3329     prout(_("TOTAL SCORE                               %5d") % game.score)
3330
3331 def plaque():
3332     "Emit winner's commemmorative plaque."
3333     skip(2)
3334     while True:
3335         proutn(_("File or device name for your plaque: "))
3336         winner = cgetline()
3337         try:
3338             fp = open(winner, "w")
3339             break
3340         except IOError:
3341             prout(_("Invalid name."))
3342
3343     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3344     winner = cgetline()
3345     # The 38 below must be 64 for 132-column paper
3346     nskip = 38 - len(winner)/2
3347     # This is where the ASCII art picture was emitted.
3348     # It got garbled somewhere in the chain of transmission to the Almy version.
3349     # We should restore it if we can find old enough FORTRAN sources.
3350     fp.write("\n\n\n")
3351     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3352     fp.write("\n\n\n\n")
3353     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3354     fp.write("\n")
3355     fp.write(_("                                                Starfleet Command bestows to you\n"))
3356     fp.write("\n")
3357     fp.write("%*s%s\n\n" % (nskip, "", winner))
3358     fp.write(_("                                                           the rank of\n\n"))
3359     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3360     fp.write("                                                          ")
3361     if game.skill ==  SKILL_EXPERT:
3362         fp.write(_(" Expert level\n\n"))
3363     elif game.skill == SKILL_EMERITUS:
3364         fp.write(_("Emeritus level\n\n"))
3365     else:
3366         fp.write(_(" Cheat level\n\n"))
3367     timestring = time.ctime()
3368     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3369              (timestring+4, timestring+20, timestring+11))
3370     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3371     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3372     fp.close()
3373
3374 # Code from io.c begins here
3375
3376 rows = linecount = 0        # for paging
3377 stdscr = None
3378 replayfp = None
3379 fullscreen_window = None
3380 srscan_window     = None   # Short range scan
3381 report_window     = None   # Report legends for status window
3382 status_window     = None   # The status window itself
3383 lrscan_window     = None   # Long range scan
3384 message_window    = None   # Main window for scrolling text
3385 prompt_window     = None   # Prompt window at bottom of display
3386 curwnd = None
3387
3388 def iostart():
3389     global stdscr, rows
3390     # for some recent versions of python2, the following enables UTF8
3391     # for the older ones we probably need to set C locale, and python3
3392     # has no problems at all
3393     if sys.version_info[0] < 3:
3394         locale.setlocale(locale.LC_ALL, "")
3395     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3396     gettext.textdomain("sst")
3397     if not (game.options & OPTION_CURSES):
3398         ln_env = os.getenv("LINES")
3399         if ln_env:
3400             rows = ln_env
3401         else:
3402             rows = 25
3403     else:
3404         stdscr = curses.initscr()
3405         stdscr.keypad(True)
3406         curses.nonl()
3407         curses.cbreak()
3408         if game.options & OPTION_COLOR:
3409             curses.start_color()
3410             curses.use_default_colors()
3411             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3412             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3413             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3414             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3415             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3416             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3417             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3418             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3419         global fullscreen_window, srscan_window, report_window, status_window
3420         global lrscan_window, message_window, prompt_window
3421         (rows, _columns)   = stdscr.getmaxyx()
3422         fullscreen_window = stdscr
3423         srscan_window     = curses.newwin(12, 25, 0,       0)
3424         report_window     = curses.newwin(11, 0,  1,       25)
3425         status_window     = curses.newwin(10, 0,  1,       39)
3426         lrscan_window     = curses.newwin(5,  0,  0,       64)
3427         message_window    = curses.newwin(0,  0,  12,      0)
3428         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3429         message_window.scrollok(True)
3430         setwnd(fullscreen_window)
3431
3432 def ioend():
3433     "Wrap up I/O."
3434     if game.options & OPTION_CURSES:
3435         stdscr.keypad(False)
3436         curses.echo()
3437         curses.nocbreak()
3438         curses.endwin()
3439
3440 def waitfor():
3441     "Wait for user action -- OK to do nothing if on a TTY"
3442     if game.options & OPTION_CURSES:
3443         stdscr.getch()
3444
3445 def announce():
3446     skip(1)
3447     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3448     skip(1)
3449
3450 def pause_game():
3451     if game.skill > SKILL_FAIR:
3452         prompt = _("[CONTINUE?]")
3453     else:
3454         prompt = _("[PRESS ENTER TO CONTINUE]")
3455
3456     if game.options & OPTION_CURSES:
3457         drawmaps(0)
3458         setwnd(prompt_window)
3459         prompt_window.clear()
3460         prompt_window.addstr(prompt)
3461         prompt_window.getstr()
3462         prompt_window.clear()
3463         prompt_window.refresh()
3464         setwnd(message_window)
3465     else:
3466         global linecount
3467         sys.stdout.write('\n')
3468         proutn(prompt)
3469         if not replayfp:
3470             my_input()
3471         sys.stdout.write('\n' * rows)
3472         linecount = 0
3473
3474 def skip(i):
3475     "Skip i lines.  Pause game if this would cause a scrolling event."
3476     for _dummy in range(i):
3477         if game.options & OPTION_CURSES:
3478             (y, _x) = curwnd.getyx()
3479             try:
3480                 curwnd.move(y+1, 0)
3481             except curses.error:
3482                 pass
3483         else:
3484             global linecount
3485             linecount += 1
3486             if rows and linecount >= rows:
3487                 pause_game()
3488             else:
3489                 sys.stdout.write('\n')
3490
3491 def proutn(proutntline):
3492     "Utter a line with no following line feed."
3493     if game.options & OPTION_CURSES:
3494         (y, x) = curwnd.getyx()
3495         (my, _mx) = curwnd.getmaxyx()
3496         if curwnd == message_window and y >= my - 2:
3497             pause_game()
3498             clrscr()
3499         if logfp and game.cdebug:
3500             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3501         curwnd.addstr(proutntline)
3502         curwnd.refresh()
3503     else:
3504         sys.stdout.write(proutntline)
3505         sys.stdout.flush()
3506
3507 def prout(proutline):
3508     proutn(proutline)
3509     skip(1)
3510
3511 def prouts(proutsline):
3512     "Emit slowly!"
3513     for c in proutsline:
3514         if not replayfp or replayfp.closed:        # Don't slow down replays
3515             time.sleep(0.03)
3516         proutn(c)
3517         if game.options & OPTION_CURSES:
3518             curwnd.refresh()
3519         else:
3520             sys.stdout.flush()
3521     if not replayfp or replayfp.closed:
3522         time.sleep(0.03)
3523
3524 def cgetline():
3525     "Get a line of input."
3526     if game.options & OPTION_CURSES:
3527         linein = codecs.decode(curwnd.getstr()) + "\n"
3528         curwnd.refresh()
3529     else:
3530         if replayfp and not replayfp.closed:
3531             while True:
3532                 linein = replayfp.readline()
3533                 proutn(linein)
3534                 if linein == '':
3535                     prout("*** Replay finished")
3536                     replayfp.close()
3537                     break
3538                 elif linein[0] != "#":
3539                     break
3540         else:
3541             try:
3542                 linein = my_input() + "\n"
3543             except EOFError:
3544                 prout("")
3545                 sys.exit(0)
3546     if logfp:
3547         logfp.write(linein)
3548     return linein
3549
3550 def setwnd(wnd):
3551     "Change windows -- OK for this to be a no-op in tty mode."
3552     global curwnd
3553     if game.options & OPTION_CURSES:
3554         if game.cdebug and logfp:
3555             if wnd == fullscreen_window:
3556                 legend = "fullscreen"
3557             elif wnd == srscan_window:
3558                 legend = "srscan"
3559             elif wnd == report_window:
3560                 legend = "report"
3561             elif wnd == status_window:
3562                 legend = "status"
3563             elif wnd == lrscan_window:
3564                 legend = "lrscan"
3565             elif wnd == message_window:
3566                 legend = "message"
3567             elif wnd == prompt_window:
3568                 legend = "prompt"
3569             else:
3570                 legend = "unknown"
3571             logfp.write("#curses: setwnd(%s)\n" % legend)
3572         curwnd = wnd
3573         # Some curses implementations get confused when you try this.
3574         try:
3575             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3576         except curses.error:
3577             pass
3578
3579 def clreol():
3580     "Clear to end of line -- can be a no-op in tty mode"
3581     if game.options & OPTION_CURSES:
3582         curwnd.clrtoeol()
3583         curwnd.refresh()
3584
3585 def clrscr():
3586     "Clear screen -- can be a no-op in tty mode."
3587     global linecount
3588     if game.options & OPTION_CURSES:
3589         curwnd.clear()
3590         curwnd.move(0, 0)
3591         curwnd.refresh()
3592     linecount = 0
3593
3594 def textcolor(color=DEFAULT):
3595     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3596         if color == DEFAULT:
3597             curwnd.attrset(0)
3598         elif color ==  BLACK:
3599             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3600         elif color ==  BLUE:
3601             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3602         elif color ==  GREEN:
3603             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3604         elif color ==  CYAN:
3605             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3606         elif color ==  RED:
3607             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3608         elif color ==  MAGENTA:
3609             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3610         elif color ==  BROWN:
3611             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3612         elif color ==  LIGHTGRAY:
3613             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3614         elif color ==  DARKGRAY:
3615             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3616         elif color ==  LIGHTBLUE:
3617             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3618         elif color ==  LIGHTGREEN:
3619             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3620         elif color ==  LIGHTCYAN:
3621             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3622         elif color ==  LIGHTRED:
3623             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3624         elif color ==  LIGHTMAGENTA:
3625             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3626         elif color ==  YELLOW:
3627             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3628         elif color ==  WHITE:
3629             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3630
3631 def highvideo():
3632     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3633         curwnd.attron(curses.A_REVERSE)
3634
3635 #
3636 # Things past this point have policy implications.
3637 #
3638
3639 def drawmaps(mode):
3640     "Hook to be called after moving to redraw maps."
3641     if game.options & OPTION_CURSES:
3642         if mode == 1:
3643             sensor()
3644         setwnd(srscan_window)
3645         curwnd.move(0, 0)
3646         srscan()
3647         if mode != 2:
3648             setwnd(status_window)
3649             status_window.clear()
3650             status_window.move(0, 0)
3651             setwnd(report_window)
3652             report_window.clear()
3653             report_window.move(0, 0)
3654             status()
3655             setwnd(lrscan_window)
3656             lrscan_window.clear()
3657             lrscan_window.move(0, 0)
3658             lrscan(silent=False)
3659
3660 def put_srscan_sym(w, sym):
3661     "Emit symbol for short-range scan."
3662     srscan_window.move(w.i+1, w.j*2+2)
3663     srscan_window.addch(sym)
3664     srscan_window.refresh()
3665
3666 def boom(w):
3667     "Enemy fall down, go boom."
3668     if game.options & OPTION_CURSES:
3669         drawmaps(0)
3670         setwnd(srscan_window)
3671         srscan_window.attron(curses.A_REVERSE)
3672         put_srscan_sym(w, game.quad[w.i][w.j])
3673         #sound(500)
3674         #time.sleep(1.0)
3675         #nosound()
3676         srscan_window.attroff(curses.A_REVERSE)
3677         put_srscan_sym(w, game.quad[w.i][w.j])
3678         curses.delay_output(500)
3679         setwnd(message_window)
3680
3681 def warble():
3682     "Sound and visual effects for teleportation."
3683     if game.options & OPTION_CURSES:
3684         drawmaps(2)
3685         setwnd(message_window)
3686         #sound(50)
3687     prouts("     . . . . .     ")
3688     if game.options & OPTION_CURSES:
3689         #curses.delay_output(1000)
3690         #nosound()
3691         pass
3692
3693 def tracktorpedo(w, step, i, n, iquad):
3694     "Torpedo-track animation."
3695     if not game.options & OPTION_CURSES:
3696         if step == 1:
3697             if n != 1:
3698                 skip(1)
3699                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3700             else:
3701                 skip(1)
3702                 proutn(_("Torpedo track- "))
3703         elif step in {4, 9}:
3704             skip(1)
3705         proutn("%s   " % w)
3706     else:
3707         if not damaged(DSRSENS) or game.condition=="docked":
3708             if i != 0 and step == 1:
3709                 drawmaps(2)
3710                 time.sleep(0.4)
3711             if iquad in {'.', ' '}:
3712                 put_srscan_sym(w, '+')
3713                 #sound(step*10)
3714                 #time.sleep(0.1)
3715                 #nosound()
3716                 put_srscan_sym(w, iquad)
3717             else:
3718                 curwnd.attron(curses.A_REVERSE)
3719                 put_srscan_sym(w, iquad)
3720                 #sound(500)
3721                 #time.sleep(1.0)
3722                 #nosound()
3723                 curwnd.attroff(curses.A_REVERSE)
3724                 put_srscan_sym(w, iquad)
3725         else:
3726             proutn("%s   " % w)
3727
3728 def makechart():
3729     "Display the current galaxy chart."
3730     if game.options & OPTION_CURSES:
3731         setwnd(message_window)
3732         message_window.clear()
3733     chart()
3734     if game.options & OPTION_TTY:
3735         skip(1)
3736
3737 NSYM        = 14
3738
3739 def prstat(txt, data):
3740     proutn(txt)
3741     if game.options & OPTION_CURSES:
3742         skip(1)
3743         setwnd(status_window)
3744     else:
3745         proutn(" " * (NSYM - len(txt)))
3746     proutn(data)
3747     skip(1)
3748     if game.options & OPTION_CURSES:
3749         setwnd(report_window)
3750
3751 # Code from moving.c begins here
3752
3753 def imove(icourse=None, noattack=False):
3754     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3755     w = Coord()
3756
3757     def newquadrant(noattack):
3758         # Leaving quadrant -- allow final enemy attack
3759         # Don't set up attack if being pushed by nova or cloaked
3760         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3761             newcnd()
3762             for enemy in game.enemies:
3763                 finald = (w - enemy.location).distance()
3764                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3765             # Stas Sergeev added the condition
3766             # that attacks only happen if Klingons
3767             # are present and your skill is good.
3768             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3769                 attack(torps_ok=False)
3770             if game.alldone:
3771                 return
3772         # check for edge of galaxy
3773         kinks = 0
3774         while True:
3775             kink = False
3776             if icourse.final.i < 0:
3777                 icourse.final.i = -icourse.final.i
3778                 kink = True
3779             if icourse.final.j < 0:
3780                 icourse.final.j = -icourse.final.j
3781                 kink = True
3782             if icourse.final.i >= GALSIZE*QUADSIZE:
3783                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3784                 kink = True
3785             if icourse.final.j >= GALSIZE*QUADSIZE:
3786                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3787                 kink = True
3788             if kink:
3789                 kinks += 1
3790             else:
3791                 break
3792         if kinks:
3793             game.nkinks += 1
3794             if game.nkinks == 3:
3795                 # Three strikes -- you're out!
3796                 finish(FNEG3)
3797                 return
3798             skip(1)
3799             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3800             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3801             prout(_("YOU WILL BE DESTROYED."))
3802         # Compute final position in new quadrant
3803         if trbeam: # Don't bother if we are to be beamed
3804             return
3805         game.quadrant = icourse.final.quadrant()
3806         game.sector = icourse.final.sector()
3807         skip(1)
3808         prout(_("Entering Quadrant %s.") % game.quadrant)
3809         game.quad[game.sector.i][game.sector.j] = game.ship
3810         newqad()
3811         if game.skill>SKILL_NOVICE:
3812             attack(torps_ok=False)
3813
3814     def check_collision(h):
3815         iquad = game.quad[h.i][h.j]
3816         if iquad != '.':
3817             # object encountered in flight path
3818             stopegy = 50.0*icourse.distance/game.optime
3819             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3820                 for enemy in game.enemies:
3821                     if enemy.location == game.sector:
3822                         collision(rammed=False, enemy=enemy)
3823                         return True
3824                 # This should not happen
3825                 prout(_("Which way did he go?"))
3826                 return False
3827             elif iquad == ' ':
3828                 skip(1)
3829                 prouts(_("***RED ALERT!  RED ALERT!"))
3830                 skip(1)
3831                 proutn("***" + crmshp())
3832                 proutn(_(" pulled into black hole at Sector %s") % h)
3833                 # Getting pulled into a black hole was certain
3834                 # death in Almy's original.  Stas Sergeev added a
3835                 # possibility that you'll get timewarped instead.
3836                 n=0
3837                 for m in range(NDEVICES):
3838                     if game.damage[m]>0:
3839                         n += 1
3840                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3841                 if (game.options & OPTION_BLKHOLE) and rnd.withprob(1-probf):
3842                     timwrp()
3843                 else:
3844                     finish(FHOLE)
3845                 return True
3846             else:
3847                 # something else
3848                 skip(1)
3849                 proutn(crmshp())
3850                 if iquad == '#':
3851                     prout(_(" encounters Tholian web at %s;") % h)
3852                 else:
3853                     prout(_(" blocked by object at %s;") % h)
3854                 proutn(_("Emergency stop required "))
3855                 prout(_("%2d units of energy.") % int(stopegy))
3856                 game.energy -= stopegy
3857                 if game.energy <= 0:
3858                     finish(FNRG)
3859                 return True
3860         return False
3861
3862     trbeam = False
3863     if game.inorbit:
3864         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3865         game.inorbit = False
3866     # If tractor beam is to occur, don't move full distance
3867     if game.state.date+game.optime >= scheduled(FTBEAM):
3868         if game.iscloaked:
3869             # We can't be tractor beamed if cloaked,
3870             # so move the event into the future
3871             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
3872         else:
3873             trbeam = True
3874             game.condition = "red"
3875             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3876             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3877     # Move out
3878     game.quad[game.sector.i][game.sector.j] = '.'
3879     for _m in range(icourse.moves):
3880         icourse.nexttok()
3881         w = icourse.sector()
3882         if icourse.origin.quadrant() != icourse.location.quadrant():
3883             newquadrant(noattack)
3884             break
3885         elif check_collision(w):
3886             prout(_("Collision detected"))
3887             break
3888         else:
3889             game.sector = w
3890     # We're in destination quadrant -- compute new average enemy distances
3891     game.quad[game.sector.i][game.sector.j] = game.ship
3892     if game.enemies:
3893         for enemy in game.enemies:
3894             finald = (w-enemy.location).distance()
3895             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3896             enemy.kdist = finald
3897         sortenemies()
3898         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3899             attack(torps_ok=False)
3900         for enemy in game.enemies:
3901             enemy.kavgd = enemy.kdist
3902     newcnd()
3903     drawmaps(0)
3904     setwnd(message_window)
3905
3906 def dock(verbose):
3907     "Dock our ship at a starbase."
3908     scanner.chew()
3909     if game.condition == "docked" and verbose:
3910         prout(_("Already docked."))
3911         return
3912     if game.inorbit:
3913         prout(_("You must first leave standard orbit."))
3914         return
3915     if game.base is None or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3916         prout(crmshp() + _(" not adjacent to base."))
3917         return
3918     if game.iscloaked:
3919         prout(_("You cannot dock while cloaked."))
3920         return
3921     game.condition = "docked"
3922     if verbose:
3923         prout(_("Docked."))
3924     game.ididit = True
3925     if game.energy < game.inenrg:
3926         game.energy = game.inenrg
3927     game.shield = game.inshld
3928     game.torps = game.intorps
3929     game.lsupres = game.inlsr
3930     game.state.crew = FULLCREW
3931     if game.brigcapacity-game.brigfree > 0:
3932         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3933         game.kcaptured += game.brigcapacity-game.brigfree
3934         game.brigfree = game.brigcapacity
3935     if communicating() and \
3936         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3937         # get attack report from base
3938         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3939         attackreport(False)
3940         game.iseenit = True
3941
3942 def cartesian(loc1=None, loc2=None):
3943     if loc1 is None:
3944         return game.quadrant * QUADSIZE + game.sector
3945     elif loc2 is None:
3946         return game.quadrant * QUADSIZE + loc1
3947     else:
3948         return loc1 * QUADSIZE + loc2
3949
3950 def getcourse(isprobe):
3951     "Get a course and distance from the user."
3952     key = ""
3953     dquad = copy.copy(game.quadrant)
3954     navmode = "unspecified"
3955     itemp = "curt"
3956     dsect = Coord()
3957     iprompt = False
3958     if game.landed and not isprobe:
3959         prout(_("Dummy! You can't leave standard orbit until you"))
3960         proutn(_("are back aboard the ship."))
3961         scanner.chew()
3962         raise TrekError
3963     while navmode == "unspecified":
3964         if damaged(DNAVSYS):
3965             if isprobe:
3966                 prout(_("Computer damaged; manual navigation only"))
3967             else:
3968                 prout(_("Computer damaged; manual movement only"))
3969             scanner.chew()
3970             navmode = "manual"
3971             key = "IHEOL"
3972             break
3973         key = scanner.nexttok()
3974         if key == "IHEOL":
3975             proutn(_("Manual or automatic- "))
3976             iprompt = True
3977             scanner.chew()
3978         elif key == "IHALPHA":
3979             if scanner.sees("manual"):
3980                 navmode = "manual"
3981                 key = scanner.nexttok()
3982                 break
3983             elif scanner.sees("automatic"):
3984                 navmode = "automatic"
3985                 key = scanner.nexttok()
3986                 break
3987             else:
3988                 huh()
3989                 scanner.chew()
3990                 raise TrekError
3991         else: # numeric
3992             if isprobe:
3993                 prout(_("(Manual navigation assumed.)"))
3994             else:
3995                 prout(_("(Manual movement assumed.)"))
3996             navmode = "manual"
3997             break
3998     delta = Coord()
3999     if navmode == "automatic":
4000         while key == "IHEOL":
4001             if isprobe:
4002                 proutn(_("Target quadrant or quadrant&sector- "))
4003             else:
4004                 proutn(_("Destination sector or quadrant&sector- "))
4005             scanner.chew()
4006             iprompt = True
4007             key = scanner.nexttok()
4008         if key != "IHREAL":
4009             huh()
4010             raise TrekError
4011         xi = int(round(scanner.real))-1
4012         key = scanner.nexttok()
4013         if key != "IHREAL":
4014             huh()
4015             raise TrekError
4016         xj = int(round(scanner.real))-1
4017         key = scanner.nexttok()
4018         if key == "IHREAL":
4019             # both quadrant and sector specified
4020             xk = int(round(scanner.real))-1
4021             key = scanner.nexttok()
4022             if key != "IHREAL":
4023                 huh()
4024                 raise TrekError
4025             xl = int(round(scanner.real))-1
4026             dquad.i = xi
4027             dquad.j = xj
4028             dsect.i = xk
4029             dsect.j = xl
4030         else:
4031             # only one pair of numbers was specified
4032             if isprobe:
4033                 # only quadrant specified -- go to center of dest quad
4034                 dquad.i = xi
4035                 dquad.j = xj
4036                 dsect.j = dsect.i = 4        # preserves 1-origin behavior
4037             else:
4038                 # only sector specified
4039                 dsect.i = xi
4040                 dsect.j = xj
4041             itemp = "normal"
4042         if not dquad.valid_quadrant() or not dsect.valid_sector():
4043             huh()
4044             raise TrekError
4045         skip(1)
4046         if not isprobe:
4047             if itemp > "curt":
4048                 if iprompt:
4049                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
4050             else:
4051                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
4052         # the actual deltas get computed here
4053         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
4054         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
4055     else: # manual
4056         while key == "IHEOL":
4057             proutn(_("X and Y displacements- "))
4058             scanner.chew()
4059             iprompt = True
4060             key = scanner.nexttok()
4061         itemp = "verbose"
4062         if key == "IHREAL":
4063             delta.j = scanner.real
4064         else:
4065             huh()
4066             raise TrekError
4067         key = scanner.nexttok()
4068         if key == "IHREAL":
4069             delta.i = scanner.real
4070         elif key == "IHEOL":
4071             delta.i = 0
4072             scanner.push("\n")
4073         else:
4074             huh()
4075             raise TrekError
4076     # Check for zero movement
4077     if delta.i == 0 and delta.j == 0:
4078         scanner.chew()
4079         raise TrekError
4080     if itemp == "verbose" and not isprobe:
4081         skip(1)
4082         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4083     scanner.chew()
4084     return course(bearing=delta.bearing(), distance=delta.distance())
4085
4086 class course:
4087     def __init__(self, bearing, distance, origin=None):
4088         self.distance = distance
4089         self.bearing = bearing
4090         if origin is None:
4091             self.origin = cartesian(game.quadrant, game.sector)
4092         else:
4093             self.origin = origin
4094         # The bearing() code we inherited from FORTRAN is actually computing
4095         # clockface directions!
4096         if self.bearing < 0.0:
4097             self.bearing += 12.0
4098         self.angle = ((15.0 - self.bearing) * 0.5235988)
4099         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4100         bigger = max(abs(self.increment.i), abs(self.increment.j))
4101         self.increment /= bigger
4102         self.moves = int(round(10*self.distance*bigger))
4103         self.reset()
4104         self.final = (self.location + self.moves*self.increment).roundtogrid()
4105         self.location = self.origin
4106         self.nextlocation = None
4107     def reset(self):
4108         self.location = self.origin
4109         self.step = 0
4110     def arrived(self):
4111         return self.location.roundtogrid() == self.final
4112     def nexttok(self):
4113         "Next step on course."
4114         self.step += 1
4115         self.nextlocation = self.location + self.increment
4116         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4117         self.location = self.nextlocation
4118         return samequad
4119     def quadrant(self):
4120         return self.location.quadrant()
4121     def sector(self):
4122         return self.location.sector()
4123     def power(self, w):
4124         return self.distance*(w**3)*(game.shldup+1)
4125     def time(self, w):
4126         return 10.0*self.distance/w**2
4127
4128 def impulse():
4129     "Move under impulse power."
4130     game.ididit = False
4131     if damaged(DIMPULS):
4132         scanner.chew()
4133         skip(1)
4134         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4135         return
4136     if game.energy > 30.0:
4137         try:
4138             icourse = getcourse(isprobe=False)
4139         except TrekError:
4140             return
4141         power = 20.0 + 100.0*icourse.distance
4142     else:
4143         power = 30.0
4144     if power >= game.energy:
4145         # Insufficient power for trip
4146         skip(1)
4147         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4148         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4149         if game.energy > 30:
4150             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4151                    int(0.01 * (game.energy-20.0)-0.05))
4152             prout(_(" quadrants.\""))
4153         else:
4154             prout(_("quadrant.  They are, therefore, useless.\""))
4155         scanner.chew()
4156         return
4157     # Make sure enough time is left for the trip
4158     game.optime = icourse.distance/0.095
4159     if game.optime >= game.state.remtime:
4160         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4161         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4162         proutn(_("we dare spend the time?\" "))
4163         if not ja():
4164             return
4165     # Activate impulse engines and pay the cost
4166     imove(icourse, noattack=False)
4167     game.ididit = True
4168     if game.alldone:
4169         return
4170     power = 20.0 + 100.0*icourse.distance
4171     game.energy -= power
4172     game.optime = icourse.distance/0.095
4173     if game.energy <= 0:
4174         finish(FNRG)
4175     return
4176
4177 def warp(wcourse, involuntary):
4178     "ove under warp drive."
4179     blooey = False; twarp = False
4180     if not involuntary: # Not WARPX entry
4181         game.ididit = False
4182         if game.iscloaked:
4183             scanner.chew()
4184             skip(1)
4185             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4186             return
4187         if game.damage[DWARPEN] > 10.0:
4188             scanner.chew()
4189             skip(1)
4190             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4191             return
4192         if damaged(DWARPEN) and game.warpfac > 4.0:
4193             scanner.chew()
4194             skip(1)
4195             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4196             prout(_("  is repaired, I can only give you warp 4.\""))
4197             return
4198                # Read in course and distance
4199         if wcourse is None:
4200             try:
4201                 wcourse = getcourse(isprobe=False)
4202             except TrekError:
4203                 return
4204         # Make sure starship has enough energy for the trip
4205         # Note: this formula is slightly different from the C version,
4206         # and lets you skate a bit closer to the edge.
4207         if wcourse.power(game.warpfac) >= game.energy:
4208             # Insufficient power for trip
4209             game.ididit = False
4210             skip(1)
4211             prout(_("Engineering to bridge--"))
4212             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4213                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4214                 if iwarp <= 0:
4215                     prout(_("We can't do it, Captain. We don't have enough energy."))
4216                 else:
4217                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4218                     if game.shldup:
4219                         prout(",")
4220                         prout(_("if you'll lower the shields."))
4221                     else:
4222                         prout(".")
4223             else:
4224                 prout(_("We haven't the energy to go that far with the shields up."))
4225             return
4226         # Make sure enough time is left for the trip
4227         game.optime = wcourse.time(game.warpfac)
4228         if game.optime >= 0.8*game.state.remtime:
4229             skip(1)
4230             prout(_("First Officer Spock- \"Captain, I compute that such"))
4231             proutn(_("  a trip would require approximately %2.0f") %
4232                    (100.0*game.optime/game.state.remtime))
4233             prout(_(" percent of our"))
4234             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4235             if not ja():
4236                 game.ididit = False
4237                 game.optime=0
4238                 return
4239     # Entry WARPX
4240     if game.warpfac > 6.0:
4241         # Decide if engine damage will occur
4242         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4243         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4244         if prob > rnd.real():
4245             blooey = True
4246             wcourse.distance = rnd.real(wcourse.distance)
4247         # Decide if time warp will occur
4248         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > rnd.real():
4249             twarp = True
4250         if game.idebug and game.warpfac==10 and not twarp:
4251             blooey = False
4252             proutn("=== Force time warp? ")
4253             if ja():
4254                 twarp = True
4255         if blooey or twarp:
4256             # If time warp or engine damage, check path
4257             # If it is obstructed, don't do warp or damage
4258             look = wcourse.moves
4259             while look > 0:
4260                 look -= 1
4261                 wcourse.nexttok()
4262                 w = wcourse.sector()
4263                 if not w.valid_sector():
4264                     break
4265                 if game.quad[w.i][w.j] != '.':
4266                     blooey = False
4267                     twarp = False
4268             wcourse.reset()
4269     # Activate Warp Engines and pay the cost
4270     imove(wcourse, noattack=False)
4271     if game.alldone:
4272         return
4273     game.energy -= wcourse.power(game.warpfac)
4274     if game.energy <= 0:
4275         finish(FNRG)
4276     game.optime = wcourse.time(game.warpfac)
4277     if twarp:
4278         timwrp()
4279     if blooey:
4280         game.damage[DWARPEN] = game.damfac * rnd.real(1.0, 4.0)
4281         skip(1)
4282         prout(_("Engineering to bridge--"))
4283         prout(_("  Scott here.  The warp engines are damaged."))
4284         prout(_("  We'll have to reduce speed to warp 4."))
4285     game.ididit = True
4286     return
4287
4288 def setwarp():
4289     "Change the warp factor."
4290     while True:
4291         key=scanner.nexttok()
4292         if key != "IHEOL":
4293             break
4294         scanner.chew()
4295         proutn(_("Warp factor- "))
4296     if key != "IHREAL":
4297         huh()
4298         return
4299     if game.damage[DWARPEN] > 10.0:
4300         prout(_("Warp engines inoperative."))
4301         return
4302     if damaged(DWARPEN) and scanner.real > 4.0:
4303         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4304         prout(_("  but right now we can only go warp 4.\""))
4305         return
4306     if scanner.real > 10.0:
4307         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4308         return
4309     if scanner.real < 1.0:
4310         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4311         return
4312     oldfac = game.warpfac
4313     game.warpfac = scanner.real
4314     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4315         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4316               int(game.warpfac))
4317         return
4318     if game.warpfac < 8.00:
4319         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4320         return
4321     if game.warpfac == 10.0:
4322         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4323         return
4324     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4325     return
4326
4327 def atover(igrab):
4328     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4329     scanner.chew()
4330     # is captain on planet?
4331     if game.landed:
4332         if damaged(DTRANSP):
4333             finish(FPNOVA)
4334             return
4335         prout(_("Scotty rushes to the transporter controls."))
4336         if game.shldup:
4337             prout(_("But with the shields up it's hopeless."))
4338             finish(FPNOVA)
4339         prouts(_("His desperate attempt to rescue you . . ."))
4340         if rnd.withprob(0.5):
4341             prout(_("fails."))
4342             finish(FPNOVA)
4343             return
4344         prout(_("SUCCEEDS!"))
4345         if game.imine:
4346             game.imine = False
4347             proutn(_("The crystals mined were "))
4348             if rnd.withprob(0.25):
4349                 prout(_("lost."))
4350             else:
4351                 prout(_("saved."))
4352                 game.icrystl = True
4353     if igrab:
4354         return
4355     # Check to see if captain in shuttle craft
4356     if game.icraft:
4357         finish(FSTRACTOR)
4358     if game.alldone:
4359         return
4360     # Inform captain of attempt to reach safety
4361     skip(1)
4362     while True:
4363         if game.justin:
4364             prouts(_("***RED ALERT!  RED ALERT!"))
4365             skip(1)
4366             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4367             prouts(_("   a supernova."))
4368             skip(2)
4369         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4370         prout(_("safely out of quadrant."))
4371         if not damaged(DRADIO):
4372             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4373         # Try to use warp engines
4374         if damaged(DWARPEN):
4375             skip(1)
4376             prout(_("Warp engines damaged."))
4377             finish(FSNOVAED)
4378             return
4379         game.warpfac = rnd.real(6.0, 8.0)
4380         prout(_("Warp factor set to %d") % int(game.warpfac))
4381         power = 0.75*game.energy
4382         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4383         dist = max(dist, rnd.real(math.sqrt(2)))
4384         bugout = course(bearing=rnd.real(12), distance=dist)        # How dumb!
4385         game.optime = bugout.time(game.warpfac)
4386         game.justin = False
4387         game.inorbit = False
4388         warp(bugout, involuntary=True)
4389         if not game.justin:
4390             # This is bad news, we didn't leave quadrant.
4391             if game.alldone:
4392                 return
4393             skip(1)
4394             prout(_("Insufficient energy to leave quadrant."))
4395             finish(FSNOVAED)
4396             return
4397         # Repeat if another snova
4398         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4399             break
4400     if game.unwon()==0:
4401         finish(FWON) # Snova killed remaining enemy.
4402
4403 def timwrp():
4404     "Let's do the time warp again."
4405     prout(_("***TIME WARP ENTERED."))
4406     if game.state.snap and rnd.withprob(0.5):
4407         # Go back in time
4408         prout(_("You are traveling backwards in time %d stardates.") %
4409               int(game.state.date-game.snapsht.date))
4410         game.state = game.snapsht
4411         game.state.snap = False
4412         if len(game.state.kcmdr):
4413             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4414             schedule(FBATTAK, expran(0.3*game.intime))
4415         schedule(FSNOVA, expran(0.5*game.intime))
4416         # next snapshot will be sooner
4417         schedule(FSNAP, expran(0.25*game.state.remtime))
4418
4419         if game.state.nscrem:
4420             schedule(FSCMOVE, 0.2777)
4421         game.isatb = 0
4422         unschedule(FCDBAS)
4423         unschedule(FSCDBAS)
4424         game.battle.invalidate()
4425         # Make sure Galileo is consistant -- Snapshot may have been taken
4426         # when on planet, which would give us two Galileos!
4427         gotit = False
4428         for l in range(game.inplan):
4429             if game.state.planets[l].known == "shuttle_down":
4430                 gotit = True
4431                 if game.iscraft == "onship" and game.ship=='E':
4432                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4433                     game.iscraft = "offship"
4434         # Likewise, if in the original time the Galileo was abandoned, but
4435         # was on ship earlier, it would have vanished -- let's restore it.
4436         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4437             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4438             game.iscraft = "onship"
4439         # There used to be code to do the actual reconstrction here,
4440         # but the starchart is now part of the snapshotted galaxy state.
4441         prout(_("Spock has reconstructed a correct star chart from memory"))
4442     else:
4443         # Go forward in time
4444         game.optime = expran(0.5*game.intime)
4445         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4446         # cheat to make sure no tractor beams occur during time warp
4447         postpone(FTBEAM, game.optime)
4448         game.damage[DRADIO] += game.optime
4449     newqad()
4450     events()        # Stas Sergeev added this -- do pending events
4451
4452 def probe():
4453     "Launch deep-space probe."
4454     # New code to launch a deep space probe
4455     if game.nprobes == 0:
4456         scanner.chew()
4457         skip(1)
4458         if game.ship == 'E':
4459             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4460         else:
4461             prout(_("Ye Faerie Queene has no deep space probes."))
4462         return
4463     if damaged(DDSP):
4464         scanner.chew()
4465         skip(1)
4466         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4467         return
4468     if is_scheduled(FDSPROB):
4469         scanner.chew()
4470         skip(1)
4471         if damaged(DRADIO) and game.condition != "docked":
4472             prout(_("Spock-  \"Records show the previous probe has not yet"))
4473             prout(_("   reached its destination.\""))
4474         else:
4475             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4476         return
4477     key = scanner.nexttok()
4478     if key == "IHEOL":
4479         if game.nprobes == 1:
4480             prout(_("1 probe left."))
4481         else:
4482             prout(_("%d probes left") % game.nprobes)
4483         proutn(_("Are you sure you want to fire a probe? "))
4484         if not ja():
4485             return
4486     game.isarmed = False
4487     if key == "IHALPHA" and scanner.token == "armed":
4488         game.isarmed = True
4489         key = scanner.nexttok()
4490     elif key == "IHEOL":
4491         proutn(_("Arm NOVAMAX warhead? "))
4492         game.isarmed = ja()
4493     elif key == "IHREAL":                # first element of course
4494         scanner.push(scanner.token)
4495     try:
4496         game.probe = getcourse(isprobe=True)
4497     except TrekError:
4498         return
4499     game.nprobes -= 1
4500     schedule(FDSPROB, 0.01) # Time to move one sector
4501     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4502     game.ididit = True
4503     return
4504
4505 def mayday():
4506     "Yell for help from nearest starbase."
4507     # There's more than one way to move in this game!
4508     scanner.chew()
4509     # Test for conditions which prevent calling for help
4510     if game.condition == "docked":
4511         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4512         return
4513     if damaged(DRADIO):
4514         prout(_("Subspace radio damaged."))
4515         return
4516     if not game.state.baseq:
4517         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4518         return
4519     if game.landed:
4520         prout(_("You must be aboard the %s.") % crmshp())
4521         return
4522     # OK -- call for help from nearest starbase
4523     game.nhelp += 1
4524     if game.base.i!=0:
4525         # There's one in this quadrant
4526         ddist = (game.base - game.sector).distance()
4527     else:
4528         ibq = None      # Force base-quadrant game to persist past loop
4529         ddist = FOREVER
4530         for ibq in game.state.baseq:
4531             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4532             if xdist < ddist:
4533                 ddist = xdist
4534         if ibq is None:
4535             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4536             return
4537         # Since starbase not in quadrant, set up new quadrant
4538         game.quadrant = ibq
4539         newqad()
4540     # dematerialize starship
4541     game.quad[game.sector.i][game.sector.j]='.'
4542     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4543            % (game.quadrant, crmshp()))
4544     game.sector.invalidate()
4545     for m in range(1, 5+1):
4546         w = game.base.scatter()
4547         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4548             # found one -- finish up
4549             game.sector = w
4550             break
4551     if game.sector is None:
4552         prout(_("You have been lost in space..."))
4553         finish(FMATERIALIZE)
4554         return
4555     # Give starbase three chances to rematerialize starship
4556     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4557     for m in range(1, 3+1):
4558         if m == 1: proutn(_("1st"))
4559         elif m == 2: proutn(_("2nd"))
4560         elif m == 3: proutn(_("3rd"))
4561         proutn(_(" attempt to re-materialize ") + crmshp())
4562         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4563         textcolor(RED)
4564         warble()
4565         if rnd.real() > probf:
4566             break
4567         prout(_("fails."))
4568         textcolor(DEFAULT)
4569         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 quue 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.gethostname(),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.