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