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