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