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