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