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