Infrastructure for color in the Python version (currently disabled).
[super-star-trek.git] / src / 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 ion 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 SSTDOC          = "/usr/share/doc/sst/sst.doc"
17 DOC_NAME        = "sst.doc"
18
19 def _(str): return gettext.gettext(str)
20
21 GALSIZE         = 8             # Galaxy size in quadrants
22 NINHAB          = (GALSIZE * GALSIZE / 2)       # Number of inhabited worlds
23 MAXUNINHAB      = 10            # Maximum uninhabited worlds
24 QUADSIZE        = 10            # Quadrant size in sectors
25 BASEMIN         = 2                             # Minimum starbases
26 BASEMAX         = (GALSIZE * GALSIZE / 12)      # Maximum starbases
27 MAXKLGAME       = 127           # Maximum Klingons per game
28 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
29 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong 
30 FOREVER         = 1e30          # Time for the indefinite future
31 MAXBURST        = 3             # Max # of torps you can launch in one turn
32 MINCMDR         = 10            # Minimum number of Klingon commanders
33 DOCKFAC         = 0.25          # Repair faster when docked
34 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
35
36 DEFAULT      = -1
37 BLACK        = 0
38 BLUE         = 1
39 GREEN        = 2
40 CYAN         = 3
41 RED          = 4
42 MAGENTA      = 5
43 BROWN        = 6
44 LIGHTGRAY    = 7
45 DARKGRAY     = 8
46 LIGHTBLUE    = 9
47 LIGHTGREEN   = 10
48 LIGHTCYAN    = 11
49 LIGHTRED     = 12
50 LIGHTMAGENTA = 13
51 YELLOW       = 14
52 WHITE        = 15
53
54 class TrekError:
55     pass
56
57 class coord:
58     def __init__(self, x=None, y=None):
59         self.i = x
60         self.j = y
61     def valid_quadrant(self):
62         return self.i>=0 and self.i<GALSIZE and self.j>=0 and self.j<GALSIZE
63     def valid_sector(self):
64         return self.i>=0 and self.i<QUADSIZE and self.j>=0 and self.j<QUADSIZE
65     def invalidate(self):
66         self.i = self.j = None
67     def is_valid(self):
68         return self.i != None and self.j != None
69     def __eq__(self, other):
70         return other != None and self.i == other.i and self.j == other.j
71     def __ne__(self, other):
72         return other == None or self.i != other.i or self.j != other.j
73     def __add__(self, other):
74         return coord(self.i+other.i, self.j+other.j)
75     def __sub__(self, other):
76         return coord(self.i-other.i, self.j-other.j)
77     def __mul__(self, other):
78         return coord(self.i*other, self.j*other)
79     def __rmul__(self, other):
80         return coord(self.i*other, self.j*other)
81     def __div__(self, other):
82         return coord(self.i/other, self.j/other)
83     def __mod__(self, other):
84         return coord(self.i % other, self.j % other)
85     def __rdiv__(self, other):
86         return coord(self.i/other, self.j/other)
87     def roundtogrid(self):
88         return coord(int(round(self.i)), int(round(self.j)))
89     def distance(self, other=None):
90         if not other: other = coord(0, 0)
91         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
92     def bearing(self):
93         return 1.90985*math.atan2(self.j, self.i)
94     def sgn(self):
95         s = coord()
96         if self.i == 0:
97             s.i = 0
98         else:
99             s.i = self.i / abs(self.i)
100         if self.j == 0:
101             s.j = 0
102         else:
103             s.j = self.j / abs(self.j)
104         return s
105     def quadrant(self):
106         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
107         return self.roundtogrid() / QUADSIZE
108     def sector(self):
109         return self.roundtogrid() % QUADSIZE
110     def scatter(self):
111         s = coord()
112         s.i = self.i + randrange(-1, 2)
113         s.j = self.j + randrange(-1, 2)
114         return s
115     def __str__(self):
116         if self.i == None or self.j == None:
117             return "Nowhere"
118         return "%s - %s" % (self.i+1, self.j+1)
119     __repr__ = __str__
120
121 class planet:
122     def __init__(self):
123         self.name = None        # string-valued if inhabited
124         self.quadrant = coord() # quadrant located
125         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
126         self.crystals = "absent"# could be "mined", "present", "absent"
127         self.known = "unknown"  # could be "unknown", "known", "shuttle_down"
128         self.inhabited = False  # is it inhabites?
129     def __str__(self):
130         return self.name
131
132 class quadrant:
133     def __init__(self):
134         self.stars = 0
135         self.planet = None
136         self.starbase = False
137         self.klingons = 0
138         self.romulans = 0
139         self.supernova = False
140         self.charted = False
141         self.status = "secure"  # Could be "secure", "distressed", "enslaved"
142
143 class page:
144     def __init__(self):
145         self.stars = None
146         self.starbase = None
147         self.klingons = None
148
149 def fill2d(size, fillfun):
150     "Fill an empty list in 2D."
151     lst = []
152     for i in range(size):
153         lst.append([]) 
154         for j in range(size):
155             lst[i].append(fillfun(i, j))
156     return lst
157
158 class snapshot:
159     def __init__(self):
160         self.snap = False       # snapshot taken
161         self.crew = 0           # crew complement
162         self.remkl = 0          # remaining klingons
163         self.nscrem = 0         # remaining super commanders
164         self.starkl = 0         # destroyed stars
165         self.basekl = 0         # destroyed bases
166         self.nromrem = 0        # Romulans remaining
167         self.nplankl = 0        # destroyed uninhabited planets
168         self.nworldkl = 0       # destroyed inhabited planets
169         self.planets = []       # Planet information
170         self.date = 0.0         # stardate
171         self.remres = 0         # remaining resources
172         self.remtime = 0        # remaining time
173         self.baseq = []         # Base quadrant coordinates
174         self.kcmdr = []         # Commander quadrant coordinates
175         self.kscmdr = coord()   # Supercommander quadrant coordinates
176         # the galaxy
177         self.galaxy = fill2d(GALSIZE, lambda i, j: quadrant())
178         # the starchart
179         self.chart = fill2d(GALSIZE, lambda i, j: page())
180
181 class event:
182     def __init__(self):
183         self.date = None        # A real number
184         self.quadrant = None    # A coord structure
185
186 # game options 
187 OPTION_ALL      = 0xffffffff
188 OPTION_TTY      = 0x00000001    # old interface 
189 OPTION_CURSES   = 0x00000002    # new interface 
190 OPTION_IOMODES  = 0x00000003    # cover both interfaces 
191 OPTION_PLANETS  = 0x00000004    # planets and mining 
192 OPTION_THOLIAN  = 0x00000008    # Tholians and their webs (UT 1979 version)
193 OPTION_THINGY   = 0x00000010    # Space Thingy can shoot back (Stas, 2005)
194 OPTION_PROBE    = 0x00000020    # deep-space probes (DECUS version, 1980)
195 OPTION_SHOWME   = 0x00000040    # bracket Enterprise in chart 
196 OPTION_RAMMING  = 0x00000080    # enemies may ram Enterprise (Almy)
197 OPTION_MVBADDY  = 0x00000100    # more enemies can move (Almy)
198 OPTION_BLKHOLE  = 0x00000200    # black hole may timewarp you (Stas, 2005) 
199 OPTION_BASE     = 0x00000400    # bases have good shields (Stas, 2005)
200 OPTION_WORLDS   = 0x00000800    # logic for inhabited worlds (ESR, 2006)
201 OPTION_AUTOSCAN = 0x00001000    # automatic LRSCAN before CHART (ESR, 2006)
202 OPTION_PLAIN    = 0x01000000    # user chose plain game 
203 OPTION_ALMY     = 0x02000000    # user chose Almy variant 
204 OPTION_COLOR    = 0x04000000    # enable color display (experimental, ESR, 2010)
205
206 # Define devices 
207 DSRSENS = 0
208 DLRSENS = 1
209 DPHASER = 2
210 DPHOTON = 3
211 DLIFSUP = 4
212 DWARPEN = 5
213 DIMPULS = 6
214 DSHIELD = 7
215 DRADIO  = 0
216 DSHUTTL = 9
217 DCOMPTR = 10
218 DNAVSYS = 11
219 DTRANSP = 12
220 DSHCTRL = 13
221 DDRAY   = 14
222 DDSP    = 15
223 NDEVICES= 16    # Number of devices
224
225 SKILL_NONE      = 0
226 SKILL_NOVICE    = 1
227 SKILL_FAIR      = 2
228 SKILL_GOOD      = 3
229 SKILL_EXPERT    = 4
230 SKILL_EMERITUS  = 5
231
232 def damaged(dev):       return (game.damage[dev] != 0.0)
233 def communicating():    return not damaged(DRADIO) or game.condition=="docked"
234
235 # Define future events 
236 FSPY    = 0     # Spy event happens always (no future[] entry)
237                 # can cause SC to tractor beam Enterprise
238 FSNOVA  = 1     # Supernova
239 FTBEAM  = 2     # Commander tractor beams Enterprise
240 FSNAP   = 3     # Snapshot for time warp
241 FBATTAK = 4     # Commander attacks base
242 FCDBAS  = 5     # Commander destroys base
243 FSCMOVE = 6     # Supercommander moves (might attack base)
244 FSCDBAS = 7     # Supercommander destroys base
245 FDSPROB = 8     # Move deep space probe
246 FDISTR  = 9     # Emit distress call from an inhabited world 
247 FENSLV  = 10    # Inhabited word is enslaved */
248 FREPRO  = 11    # Klingons build a ship in an enslaved system
249 NEVENTS = 12
250
251 # Abstract out the event handling -- underlying data structures will change
252 # when we implement stateful events 
253 def findevent(evtype):  return game.future[evtype]
254
255 class enemy:
256     def __init__(self, type=None, loc=None, power=None):
257         self.type = type
258         self.location = coord()
259         if loc:
260             self.move(loc)
261         self.power = power      # enemy energy level
262         game.enemies.append(self)
263     def move(self, loc):
264         motion = (loc != self.location)
265         if self.location.i is not None and self.location.j is not None:
266             if motion:
267                 if self.type == 'T':
268                     game.quad[self.location.i][self.location.j] = '#'
269                 else:
270                     game.quad[self.location.i][self.location.j] = '.'
271         if loc:
272             self.location = copy.copy(loc)
273             game.quad[self.location.i][self.location.j] = self.type
274             self.kdist = self.kavgd = (game.sector - loc).distance()
275         else:
276             self.location = coord()
277             self.kdist = self.kavgd = None
278             game.enemies.remove(self)
279         return motion
280     def __repr__(self):
281         return "<%s,%s.%f>" % (self.type, self.location, self.power)    # For debugging
282
283 class gamestate:
284     def __init__(self):
285         self.options = None     # Game options
286         self.state = snapshot() # A snapshot structure
287         self.snapsht = snapshot()       # Last snapshot taken for time-travel purposes
288         self.quad = None        # contents of our quadrant
289         self.damage = [0.0] * NDEVICES  # damage encountered
290         self.future = []                # future events
291         for i in range(NEVENTS):
292             self.future.append(event())
293         self.passwd  = None;            # Self Destruct password
294         self.enemies = []
295         self.quadrant = None    # where we are in the large
296         self.sector = None      # where we are in the small
297         self.tholian = None     # Tholian enemy object
298         self.base = None        # position of base in current quadrant
299         self.battle = None      # base coordinates being attacked
300         self.plnet = None       # location of planet in quadrant
301         self.gamewon = False    # Finished!
302         self.ididit = False     # action taken -- allows enemy to attack
303         self.alive = False      # we are alive (not killed)
304         self.justin = False     # just entered quadrant
305         self.shldup = False     # shields are up
306         self.shldchg = False    # shield is changing (affects efficiency)
307         self.iscate = False     # super commander is here
308         self.ientesc = False    # attempted escape from supercommander
309         self.resting = False    # rest time
310         self.icraft = False     # Kirk in Galileo
311         self.landed = False     # party on planet (true), on ship (false)
312         self.alldone = False    # game is now finished
313         self.neutz = False      # Romulan Neutral Zone
314         self.isarmed = False    # probe is armed
315         self.inorbit = False    # orbiting a planet
316         self.imine = False      # mining
317         self.icrystl = False    # dilithium crystals aboard
318         self.iseenit = False    # seen base attack report
319         self.thawed = False     # thawed game
320         self.condition = None   # "green", "yellow", "red", "docked", "dead"
321         self.iscraft = None     # "onship", "offship", "removed"
322         self.skill = None       # Player skill level
323         self.inkling = 0        # initial number of klingons
324         self.inbase = 0         # initial number of bases
325         self.incom = 0          # initial number of commanders
326         self.inscom = 0         # initial number of commanders
327         self.inrom = 0          # initial number of commanders
328         self.instar = 0         # initial stars
329         self.intorps = 0        # initial/max torpedoes
330         self.torps = 0          # number of torpedoes
331         self.ship = 0           # ship type -- 'E' is Enterprise
332         self.abandoned = 0      # count of crew abandoned in space
333         self.length = 0         # length of game
334         self.klhere = 0         # klingons here
335         self.casual = 0         # causalties
336         self.nhelp = 0          # calls for help
337         self.nkinks = 0         # count of energy-barrier crossings
338         self.iplnet = None      # planet # in quadrant
339         self.inplan = 0         # initial planets
340         self.irhere = 0         # Romulans in quadrant
341         self.isatb = 0          # =2 if super commander is attacking base
342         self.tourn = None       # tournament number
343         self.nprobes = 0        # number of probes available
344         self.inresor = 0.0      # initial resources
345         self.intime = 0.0       # initial time
346         self.inenrg = 0.0       # initial/max energy
347         self.inshld = 0.0       # initial/max shield
348         self.inlsr = 0.0        # initial life support resources
349         self.indate = 0.0       # initial date
350         self.energy = 0.0       # energy level
351         self.shield = 0.0       # shield level
352         self.warpfac = 0.0      # warp speed
353         self.lsupres = 0.0      # life support reserves
354         self.optime = 0.0       # time taken by current operation
355         self.damfac = 0.0       # damage factor
356         self.lastchart = 0.0    # time star chart was last updated
357         self.cryprob = 0.0      # probability that crystal will work
358         self.probe = None       # object holding probe course info
359         self.height = 0.0       # height of orbit around planet
360     def recompute(self):
361         # Stas thinks this should be (C expression): 
362         # game.state.remkl + len(game.state.kcmdr) > 0 ?
363         #       game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
364         # He says the existing expression is prone to divide-by-zero errors
365         # after killing the last klingon when score is shown -- perhaps also
366         # if the only remaining klingon is SCOM.
367         game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
368
369 FWON = 0
370 FDEPLETE = 1
371 FLIFESUP = 2
372 FNRG = 3
373 FBATTLE = 4
374 FNEG3 = 5
375 FNOVA = 6
376 FSNOVAED = 7
377 FABANDN = 8
378 FDILITHIUM = 9
379 FMATERIALIZE = 10
380 FPHASER = 11
381 FLOST = 12
382 FMINING = 13
383 FDPLANET = 14
384 FPNOVA = 15
385 FSSC = 16
386 FSTRACTOR = 17
387 FDRAY = 18
388 FTRIBBLE = 19
389 FHOLE = 20
390 FCREW = 21
391
392 def withprob(p):
393     return random.random() < p
394
395 def randrange(*args):
396     return random.randrange(*args)
397
398 def randreal(*args):
399     v = random.random()
400     if len(args) == 1:
401         v *= args[0]            # from [0, args[0])
402     elif len(args) == 2:
403         v = args[0] + v*(args[1]-args[0])       # from [args[0], args[1])
404     return v
405
406 # Code from ai.c begins here
407
408 def welcoming(iq):
409     "Would this quadrant welcome another Klingon?"
410     return iq.valid_quadrant() and \
411         not game.state.galaxy[iq.i][iq.j].supernova and \
412         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
413
414 def tryexit(enemy, look, irun):
415     "A bad guy attempts to bug out."
416     iq = coord()
417     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
418     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
419     if not welcoming(iq):
420         return False;
421     if enemy.type == 'R':
422         return False; # Romulans cannot escape! 
423     if not irun:
424         # avoid intruding on another commander's territory 
425         if enemy.type == 'C':
426             if iq in game.state.kcmdr:
427                 return False
428             # refuse to leave if currently attacking starbase 
429             if game.battle == game.quadrant:
430                 return False
431         # don't leave if over 1000 units of energy 
432         if enemy.power > 1000.0:
433             return False
434     # emit escape message and move out of quadrant.
435     # we know this if either short or long range sensors are working
436     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
437         game.condition == "docked":
438         prout(crmena(True, enemy.type, "sector", enemy.location) + \
439               (_(" escapes to Quadrant %s (and regains strength).") % q))
440     # handle local matters related to escape
441     enemy.move(None)
442     game.klhere -= 1
443     if game.condition != "docked":
444         newcnd()
445     # Handle global matters related to escape 
446     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
447     game.state.galaxy[iq.i][iq.j].klingons += 1
448     if enemy.type=='S':
449         game.iscate = False
450         game.ientesc = False
451         game.isatb = 0
452         schedule(FSCMOVE, 0.2777)
453         unschedule(FSCDBAS)
454         game.state.kscmdr=iq
455     else:
456         for cmdr in game.state.kcmdr:
457             if cmdr == game.quadrant:
458                 game.state.kcmdr[n] = iq
459                 break
460     return True; # success 
461
462 # The bad-guy movement algorithm:
463
464 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
465 # If both are operating full strength, force is 1000. If both are damaged,
466 # force is -1000. Having shields down subtracts an additional 1000.
467
468 # 2. Enemy has forces equal to the energy of the attacker plus
469 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
470 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
471
472 # Attacker Initial energy levels (nominal):
473 # Klingon   Romulan   Commander   Super-Commander
474 # Novice    400        700        1200        
475 # Fair      425        750        1250
476 # Good      450        800        1300        1750
477 # Expert    475        850        1350        1875
478 # Emeritus  500        900        1400        2000
479 # VARIANCE   75        200         200         200
480
481 # Enemy vessels only move prior to their attack. In Novice - Good games
482 # only commanders move. In Expert games, all enemy vessels move if there
483 # is a commander present. In Emeritus games all enemy vessels move.
484
485 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
486 # forces are 1000 greater than Enterprise.
487
488 # Agressive action on average cuts the distance between the ship and
489 # the enemy to 1/4 the original.
490
491 # 4.  At lower energy advantage, movement units are proportional to the
492 # advantage with a 650 advantage being to hold ground, 800 to move forward
493 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
494
495 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
496 # retreat, especially at high skill levels.
497
498 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
499
500 def movebaddy(enemy):
501     "Tactical movement for the bad guys."
502     next = coord(); look = coord()
503     irun = False
504     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
505     if game.skill >= SKILL_EXPERT:
506         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
507     else:
508         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
509     dist1 = enemy.kdist
510     mdist = int(dist1 + 0.5); # Nearest integer distance 
511     # If SC, check with spy to see if should hi-tail it 
512     if enemy.type=='S' and \
513         (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
514         irun = True
515         motion = -QUADSIZE
516     else:
517         # decide whether to advance, retreat, or hold position 
518         forces = enemy.power+100.0*len(game.enemies)+400*(nbaddys-1)
519         if not game.shldup:
520             forces += 1000; # Good for enemy if shield is down! 
521         if not damaged(DPHASER) or not damaged(DPHOTON):
522             if damaged(DPHASER): # phasers damaged 
523                 forces += 300.0
524             else:
525                 forces -= 0.2*(game.energy - 2500.0)
526             if damaged(DPHOTON): # photon torpedoes damaged 
527                 forces += 300.0
528             else:
529                 forces -= 50.0*game.torps
530         else:
531             # phasers and photon tubes both out! 
532             forces += 1000.0
533         motion = 0
534         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
535             motion = ((forces + randreal(200))/150.0) - 5.0
536         else:
537             if forces > 1000.0: # Very strong -- move in for kill 
538                 motion = (1.0 - randreal())**2 * dist1 + 1.0
539             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
540                 motion -= game.skill*(2.0-randreal()**2)
541         if idebug:
542             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
543         # don't move if no motion 
544         if motion==0:
545             return
546         # Limit motion according to skill 
547         if abs(motion) > game.skill:
548             if motion < 0:
549                 motion = -game.skill
550             else:
551                 motion = game.skill
552     # calculate preferred number of steps 
553     nsteps = abs(int(motion))
554     if motion > 0 and nsteps > mdist:
555         nsteps = mdist; # don't overshoot 
556     if nsteps > QUADSIZE:
557         nsteps = QUADSIZE; # This shouldn't be necessary 
558     if nsteps < 1:
559         nsteps = 1; # This shouldn't be necessary 
560     if idebug:
561         proutn("NSTEPS = %d:" % nsteps)
562     # Compute preferred values of delta X and Y 
563     m = game.sector - enemy.location
564     if 2.0 * abs(m.i) < abs(m.j):
565         m.i = 0
566     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
567         m.j = 0
568     m = (motion * m).sgn()
569     next = enemy.location
570     # main move loop 
571     for ll in range(nsteps):
572         if idebug:
573             proutn(" %d" % (ll+1))
574         # Check if preferred position available 
575         look = next + m
576         if m.i < 0:
577             krawli = 1
578         else:
579             krawli = -1
580         if m.j < 0:
581             krawlj = 1
582         else:
583             krawlj = -1
584         success = False
585         attempts = 0; # Settle mysterious hang problem 
586         while attempts < 20 and not success:
587             attempts += 1
588             if look.i < 0 or look.i >= QUADSIZE:
589                 if motion < 0 and tryexit(enemy, look, irun):
590                     return
591                 if krawli == m.i or m.j == 0:
592                     break
593                 look.i = next.i + krawli
594                 krawli = -krawli
595             elif look.j < 0 or look.j >= QUADSIZE:
596                 if motion < 0 and tryexit(enemy, look, irun):
597                     return
598                 if krawlj == m.j or m.i == 0:
599                     break
600                 look.j = next.j + krawlj
601                 krawlj = -krawlj
602             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
603                 # See if enemy should ram ship 
604                 if game.quad[look.i][look.j] == game.ship and \
605                     (enemy.type == 'C' or enemy.type == 'S'):
606                     collision(rammed=True, enemy=enemy)
607                     return
608                 if krawli != m.i and m.j != 0:
609                     look.i = next.i + krawli
610                     krawli = -krawli
611                 elif krawlj != m.j and m.i != 0:
612                     look.j = next.j + krawlj
613                     krawlj = -krawlj
614                 else:
615                     break; # we have failed 
616             else:
617                 success = True
618         if success:
619             next = look
620             if idebug:
621                 proutn(`next`)
622         else:
623             break; # done early 
624     if idebug:
625         skip(1)
626     if enemy.move(next):
627         if not damaged(DSRSENS) or game.condition == "docked":
628             proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
629             if enemy.kdist < dist1:
630                 proutn(_(" advances to "))
631             else:
632                 proutn(_(" retreats to "))
633             prout("Sector %s." % next)
634
635 def moveklings():
636     "Sequence Klingon tactical movement."
637     if idebug:
638         prout("== MOVCOM")
639     # Figure out which Klingon is the commander (or Supercommander)
640     # and do move
641     if game.quadrant in game.state.kcmdr:
642         for enemy in game.enemies:
643             if enemy.type == 'C':
644                 movebaddy(enemy)
645     if game.state.kscmdr==game.quadrant:
646         for enemy in game.enemies:
647             if enemy.type == 'S':
648                 movebaddy(enemy)
649                 break
650     # If skill level is high, move other Klingons and Romulans too!
651     # Move these last so they can base their actions on what the
652     # commander(s) do.
653     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
654         for enemy in game.enemies:
655             if enemy.type in ('K', 'R'):
656                 movebaddy(enemy)
657     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
658
659 def movescom(iq, avoid):
660     "Commander movement helper." 
661     # Avoid quadrants with bases if we want to avoid Enterprise 
662     if not welcoming(iq) or (avoid and iq in game.state.baseq):
663         return False
664     if game.justin and not game.iscate:
665         return False
666     # do the move 
667     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
668     game.state.kscmdr = iq
669     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
670     if game.state.kscmdr==game.quadrant:
671         # SC has scooted, remove him from current quadrant 
672         game.iscate=False
673         game.isatb=0
674         game.ientesc = False
675         unschedule(FSCDBAS)
676         for enemy in game.enemies:
677             if enemy.type == 'S':
678                 break
679         enemy.move(None)
680         game.klhere -= 1
681         if game.condition != "docked":
682             newcnd()
683         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
684     # check for a helpful planet 
685     for i in range(game.inplan):
686         if game.state.planets[i].quadrant == game.state.kscmdr and \
687             game.state.planets[i].crystals == "present":
688             # destroy the planet 
689             game.state.planets[i].pclass = "destroyed"
690             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
691             if communicating():
692                 announce()
693                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
694                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
695                 prout(_("   by the Super-commander.\""))
696             break
697     return True; # looks good! 
698                         
699 def supercommander():
700     "Move the Super Commander." 
701     iq = coord(); sc = coord(); ibq = coord(); idelta = coord()
702     basetbl = []
703     if idebug:
704         prout("== SUPERCOMMANDER")
705     # Decide on being active or passive 
706     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 \
707             (game.state.date-game.indate) < 3.0)
708     if not game.iscate and avoid:
709         # compute move away from Enterprise 
710         idelta = game.state.kscmdr-game.quadrant
711         if idelta.distance() > 2.0:
712             # circulate in space 
713             idelta.i = game.state.kscmdr.j-game.quadrant.j
714             idelta.j = game.quadrant.i-game.state.kscmdr.i
715     else:
716         # compute distances to starbases 
717         if not game.state.baseq:
718             # nothing left to do 
719             unschedule(FSCMOVE)
720             return
721         sc = game.state.kscmdr
722         for base in game.state.baseq:
723             basetbl.append((i, (base - sc).distance()))
724         if game.state.baseq > 1:
725             basetbl.sort(lambda x, y: cmp(x[1]. y[1]))
726         # look for nearest base without a commander, no Enterprise, and
727         # without too many Klingons, and not already under attack. 
728         ifindit = iwhichb = 0
729         for (i2, base) in enumerate(game.state.baseq):
730             i = basetbl[i2][0]; # bug in original had it not finding nearest
731             if base==game.quadrant or base==game.battle or not welcoming(base):
732                 continue
733             # if there is a commander, and no other base is appropriate,
734             # we will take the one with the commander
735             for cmdr in game.state.kcmdr:
736                 if base == cmdr and ifindit != 2:
737                     ifindit = 2
738                     iwhichb = i
739                     break
740             else:       # no commander -- use this one 
741                 ifindit = 1
742                 iwhichb = i
743                 break
744         if ifindit==0:
745             return # Nothing suitable -- wait until next time
746         ibq = game.state.baseq[iwhichb]
747         # decide how to move toward base 
748         idelta = ibq - game.state.kscmdr
749     # Maximum movement is 1 quadrant in either or both axes 
750     idelta = idelta.sgn()
751     # try moving in both x and y directions
752     # there was what looked like a bug in the Almy C code here,
753     # but it might be this translation is just wrong.
754     iq = game.state.kscmdr + idelta
755     if not movescom(iq, avoid):
756         # failed -- try some other maneuvers 
757         if idelta.i==0 or idelta.j==0:
758             # attempt angle move 
759             if idelta.i != 0:
760                 iq.j = game.state.kscmdr.j + 1
761                 if not movescom(iq, avoid):
762                     iq.j = game.state.kscmdr.j - 1
763                     movescom(iq, avoid)
764             elif idelta.j != 0:
765                 iq.i = game.state.kscmdr.i + 1
766                 if not movescom(iq, avoid):
767                     iq.i = game.state.kscmdr.i - 1
768                     movescom(iq, avoid)
769         else:
770             # try moving just in x or y 
771             iq.j = game.state.kscmdr.j
772             if not movescom(iq, avoid):
773                 iq.j = game.state.kscmdr.j + idelta.j
774                 iq.i = game.state.kscmdr.i
775                 movescom(iq, avoid)
776     # check for a base 
777     if len(game.state.baseq) == 0:
778         unschedule(FSCMOVE)
779     else:
780         for ibq in game.state.baseq:
781             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
782                 # attack the base 
783                 if avoid:
784                     return # no, don't attack base! 
785                 game.iseenit = False
786                 game.isatb = 1
787                 schedule(FSCDBAS, randreal(1.0, 3.0))
788                 if is_scheduled(FCDBAS):
789                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
790                 if not communicating():
791                     return # no warning 
792                 game.iseenit = True
793                 announce()
794                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
795                       % game.state.kscmdr)
796                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
797                 proutn(_("   It can survive until stardate %d.\"") \
798                        % int(scheduled(FSCDBAS)))
799                 if not game.resting:
800                     return
801                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
802                 if ja() == False:
803                     return
804                 game.resting = False
805                 game.optime = 0.0; # actually finished 
806                 return
807     # Check for intelligence report 
808     if not idebug and \
809         (withprob(0.8) or \
810          (not communicating()) or \
811          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
812         return
813     announce()
814     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
815     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
816     return
817
818 def movetholian():
819     "Move the Tholian."
820     if not game.tholian or game.justin:
821         return
822     id = coord()
823     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
824         id.i = 0; id.j = QUADSIZE-1
825     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
826         id.i = QUADSIZE-1; id.j = QUADSIZE-1
827     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
828         id.i = QUADSIZE-1; id.j = 0
829     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
830         id.i = 0; id.j = 0
831     else:
832         # something is wrong! 
833         game.tholian.move(None)
834         prout("***Internal error: Tholian in a bad spot.")
835         return
836     # do nothing if we are blocked 
837     if game.quad[id.i][id.j] not in ('.', '#'):
838         return
839     here = copy.copy(game.tholian.location)
840     delta = (id - game.tholian.location).sgn()
841     # move in x axis 
842     while here.i != id.i:
843         here.i += delta.i
844         if game.quad[here.i][here.j]=='.':
845             game.tholian.move(here)
846     # move in y axis 
847     while here.j != id.j:
848         here.j += delta.j
849         if game.quad[here.i][here.j]=='.':
850             game.tholian.move(here)
851     # check to see if all holes plugged 
852     for i in range(QUADSIZE):
853         if game.quad[0][i]!='#' and game.quad[0][i]!='T':
854             return
855         if game.quad[QUADSIZE-1][i]!='#' and game.quad[QUADSIZE-1][i]!='T':
856             return
857         if game.quad[i][0]!='#' and game.quad[i][0]!='T':
858             return
859         if game.quad[i][QUADSIZE-1]!='#' and game.quad[i][QUADSIZE-1]!='T':
860             return
861     # All plugged up -- Tholian splits 
862     game.quad[game.tholian.location.i][game.tholian.location.j]='#'
863     dropin(' ')
864     prout(crmena(True, 'T', "sector", game.tholian) + _(" completes web."))
865     game.tholian.move(None)
866     return
867
868 # Code from battle.c begins here
869
870 def doshield(shraise):
871     "Change shield status."
872     action = "NONE"
873     game.ididit = False
874     if shraise:
875         action = "SHUP"
876     else:
877         key = scanner.next()
878         if key == "IHALPHA":
879             if scanner.sees("transfer"):
880                 action = "NRG"
881             else:
882                 if damaged(DSHIELD):
883                     prout(_("Shields damaged and down."))
884                     return
885                 if scanner.sees("up"):
886                     action = "SHUP"
887                 elif scanner.sees("down"):
888                     action = "SHDN"
889         if action=="NONE":
890             proutn(_("Do you wish to change shield energy? "))
891             if ja() == True:
892                 action = "NRG"
893             elif damaged(DSHIELD):
894                 prout(_("Shields damaged and down."))
895                 return
896             elif game.shldup:
897                 proutn(_("Shields are up. Do you want them down? "))
898                 if ja() == True:
899                     action = "SHDN"
900                 else:
901                     scanner.chew()
902                     return
903             else:
904                 proutn(_("Shields are down. Do you want them up? "))
905                 if ja() == True:
906                     action = "SHUP"
907                 else:
908                     scanner.chew()
909                     return    
910     if action == "SHUP": # raise shields 
911         if game.shldup:
912             prout(_("Shields already up."))
913             return
914         game.shldup = True
915         game.shldchg = True
916         if game.condition != "docked":
917             game.energy -= 50.0
918         prout(_("Shields raised."))
919         if game.energy <= 0:
920             skip(1)
921             prout(_("Shields raising uses up last of energy."))
922             finish(FNRG)
923             return
924         game.ididit=True
925         return
926     elif action == "SHDN":
927         if not game.shldup:
928             prout(_("Shields already down."))
929             return
930         game.shldup=False
931         game.shldchg=True
932         prout(_("Shields lowered."))
933         game.ididit = True
934         return
935     elif action == "NRG":
936         while scanner.next() != "IHREAL":
937             scanner.chew()
938             proutn(_("Energy to transfer to shields- "))
939         nrg = scanner.real
940         scanner.chew()
941         if nrg == 0:
942             return
943         if nrg > game.energy:
944             prout(_("Insufficient ship energy."))
945             return
946         game.ididit = True
947         if game.shield+nrg >= game.inshld:
948             prout(_("Shield energy maximized."))
949             if game.shield+nrg > game.inshld:
950                 prout(_("Excess energy requested returned to ship energy"))
951             game.energy -= game.inshld-game.shield
952             game.shield = game.inshld
953             return
954         if nrg < 0.0 and game.energy-nrg > game.inenrg:
955             # Prevent shield drain loophole 
956             skip(1)
957             prout(_("Engineering to bridge--"))
958             prout(_("  Scott here. Power circuit problem, Captain."))
959             prout(_("  I can't drain the shields."))
960             game.ididit = False
961             return
962         if game.shield+nrg < 0:
963             prout(_("All shield energy transferred to ship."))
964             game.energy += game.shield
965             game.shield = 0.0
966             return
967         proutn(_("Scotty- \""))
968         if nrg > 0:
969             prout(_("Transferring energy to shields.\""))
970         else:
971             prout(_("Draining energy from shields.\""))
972         game.shield += nrg
973         game.energy -= nrg
974         return
975
976 def randdevice():
977     "Choose a device to damage, at random."
978     weights = (
979         105,    # DSRSENS: short range scanners 10.5% 
980         105,    # DLRSENS: long range scanners          10.5% 
981         120,    # DPHASER: phasers                      12.0% 
982         120,    # DPHOTON: photon torpedoes             12.0% 
983         25,     # DLIFSUP: life support                  2.5% 
984         65,     # DWARPEN: warp drive                    6.5% 
985         70,     # DIMPULS: impulse engines               6.5% 
986         145,    # DSHIELD: deflector shields            14.5% 
987         30,     # DRADIO:  subspace radio                3.0% 
988         45,     # DSHUTTL: shuttle                       4.5% 
989         15,     # DCOMPTR: computer                      1.5% 
990         20,     # NAVCOMP: navigation system             2.0% 
991         75,     # DTRANSP: transporter                   7.5% 
992         20,     # DSHCTRL: high-speed shield controller  2.0% 
993         10,     # DDRAY: death ray                       1.0% 
994         30,     # DDSP: deep-space probes                3.0% 
995     )
996     assert(sum(weights) == 1000)
997     idx = randrange(1000)
998     sum = 0
999     for (i, w) in enumerate(weights):
1000         sum += w
1001         if idx < sum:
1002             return i
1003     return None;        # we should never get here
1004
1005 def collision(rammed, enemy):
1006     "Collision handling fot rammong events."
1007     prouts(_("***RED ALERT!  RED ALERT!"))
1008     skip(1)
1009     prout(_("***COLLISION IMMINENT."))
1010     skip(2)
1011     proutn("***")
1012     proutn(crmshp())
1013     hardness = {'R':1.5, 'C':2.0, 'S':2.5, 'T':0.5, '?':4.0}.get(enemy.type, 1.0)
1014     if rammed:
1015         proutn(_(" rammed by "))
1016     else:
1017         proutn(_(" rams "))
1018     proutn(crmena(False, enemy.type, "sector", enemy.location))
1019     if rammed:
1020         proutn(_(" (original position)"))
1021     skip(1)
1022     deadkl(enemy.location, enemy.type, game.sector)
1023     proutn("***" + crmship() + " heavily damaged.")
1024     icas = randrange(10, 30)
1025     prout(_("***Sickbay reports %d casualties"), icas)
1026     game.casual += icas
1027     game.state.crew -= icas
1028     # In the pre-SST2K version, all devices got equiprobably damaged,
1029     # which was silly.  Instead, pick up to half the devices at
1030     # random according to our weighting table,
1031     ncrits = randrange(NDEVICES/2)
1032     for m in range(ncrits):
1033         dev = randdevice()
1034         if game.damage[dev] < 0:
1035             continue
1036         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1037         # Damage for at least time of travel! 
1038         game.damage[dev] += game.optime + extradm
1039     game.shldup = False
1040     prout(_("***Shields are down."))
1041     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1042         announce()
1043         damagereport()
1044     else:
1045         finish(FWON)
1046     return
1047
1048 def torpedo(origin, bearing, dispersion, number, nburst):
1049     "Let a photon torpedo fly" 
1050     if not damaged(DSRSENS) or game.condition=="docked":
1051         setwnd(srscan_window)
1052     else: 
1053         setwnd(message_window)
1054     ac = bearing + 0.25*dispersion      # dispersion is a random variable
1055     bullseye = (15.0 - bearing)*0.5235988
1056     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin)) 
1057     bumpto = coord(0, 0)
1058     # Loop to move a single torpedo 
1059     setwnd(message_window)
1060     for step in range(1, QUADSIZE*2):
1061         if not track.next(): break
1062         w = track.sector()
1063         if not w.valid_sector():
1064             break
1065         iquad=game.quad[w.i][w.j]
1066         tracktorpedo(origin, w, step, number, nburst, iquad)
1067         if iquad=='.':
1068             continue
1069         # hit something 
1070         if not damaged(DSRSENS) or game.condition == "docked":
1071             skip(1);    # start new line after text track 
1072         if iquad in ('E', 'F'): # Hit our ship 
1073             skip(1)
1074             prout(_("Torpedo hits %s.") % crmshp())
1075             hit = 700.0 + randreal(100) - \
1076                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1077             newcnd(); # we're blown out of dock 
1078             if game.landed or game.condition=="docked":
1079                 return hit # Cheat if on a planet 
1080             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1081             # is 143 degrees, which is almost exactly 4.8 clockface units
1082             displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
1083             displacement.next()
1084             bumpto = displacement.sector()
1085             if not bumpto.valid_sector():
1086                 return hit
1087             if game.quad[bumpto.i][bumpto.j]==' ':
1088                 finish(FHOLE)
1089                 return hit
1090             if game.quad[bumpto.i][bumpto.j]!='.':
1091                 # can't move into object 
1092                 return hit
1093             game.sector = bumpto
1094             proutn(crmshp())
1095             game.quad[w.i][w.j]='.'
1096             game.quad[bumpto.i][bumpto.j]=iquad
1097             prout(_(" displaced by blast to Sector %s ") % bumpto)
1098             for enemy in game.enemies:
1099                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1100             game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1101             return None
1102         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy 
1103             # find the enemy 
1104             if iquad in ('C', 'S') and withprob(0.05):
1105                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1106                 prout(_("   torpedo neutralized."))
1107                 return None
1108             for enemy in game.enemies:
1109                 if w == enemy.location:
1110                     break
1111             kp = math.fabs(enemy.power)
1112             h1 = 700.0 + randrange(100) - \
1113                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1114             h1 = math.fabs(h1)
1115             if kp < h1:
1116                 h1 = kp
1117             if enemy.power < 0:
1118                 enemy.power -= -h1
1119             else:
1120                 enemy.power -= h1
1121             if enemy.power == 0:
1122                 deadkl(w, iquad, w)
1123                 return None
1124             proutn(crmena(True, iquad, "sector", w))
1125             displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
1126             displacement.next()
1127             bumpto = displacement.sector()
1128             if not bumpto.valid_sector():
1129                 prout(_(" damaged but not destroyed."))
1130                 return
1131             if game.quad[bumpto.i][bumpto.j] == ' ':
1132                 prout(_(" buffeted into black hole."))
1133                 deadkl(w, iquad, bumpto)
1134             if game.quad[bumpto.i][bumpto.j] != '.':
1135                 prout(_(" damaged but not destroyed."))
1136             else:
1137                 prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1138                 enemy.location = bumpto
1139                 game.quad[w.i][w.j]='.'
1140                 game.quad[bumpto.i][bumpto.j]=iquad
1141                 for enemy in game.enemies:
1142                     enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1143                 game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1144             return None
1145         elif iquad == 'B': # Hit a base 
1146             skip(1)
1147             prout(_("***STARBASE DESTROYED.."))
1148             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
1149             game.quad[w.i][w.j]='.'
1150             game.base.invalidate()
1151             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
1152             game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
1153             game.state.basekl += 1
1154             newcnd()
1155             return None
1156         elif iquad == 'P': # Hit a planet 
1157             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1158             game.state.nplankl += 1
1159             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1160             game.iplnet.pclass = "destroyed"
1161             game.iplnet = None
1162             game.plnet.invalidate()
1163             game.quad[w.i][w.j] = '.'
1164             if game.landed:
1165                 # captain perishes on planet 
1166                 finish(FDPLANET)
1167             return None
1168         elif iquad == '@': # Hit an inhabited world -- very bad! 
1169             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1170             game.state.nworldkl += 1
1171             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1172             game.iplnet.pclass = "destroyed"
1173             game.iplnet = None
1174             game.plnet.invalidate()
1175             game.quad[w.i][w.j] = '.'
1176             if game.landed:
1177                 # captain perishes on planet 
1178                 finish(FDPLANET)
1179             prout(_("The torpedo destroyed an inhabited planet."))
1180             return None
1181         elif iquad == '*': # Hit a star 
1182             if withprob(0.9):
1183                 nova(w)
1184             else:
1185                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1186             return None
1187         elif iquad == '?': # Hit a thingy 
1188             if not (game.options & OPTION_THINGY) or withprob(0.3):
1189                 skip(1)
1190                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1191                 skip(1)
1192                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1193                 skip(1)
1194                 proutn(_("Mr. Spock-"))
1195                 prouts(_("  \"Fascinating!\""))
1196                 skip(1)
1197                 deadkl(w, iquad, w)
1198             else:
1199                 # Stas Sergeev added the possibility that
1200                 # you can shove the Thingy and piss it off.
1201                 # It then becomes an enemy and may fire at you.
1202                 thing.angry = True
1203                 shoved = True
1204             return None
1205         elif iquad == ' ': # Black hole 
1206             skip(1)
1207             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1208             return None
1209         elif iquad == '#': # hit the web 
1210             skip(1)
1211             prout(_("***Torpedo absorbed by Tholian web."))
1212             return None
1213         elif iquad == 'T':  # Hit a Tholian 
1214             h1 = 700.0 + randrange(100) - \
1215                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1216             h1 = math.fabs(h1)
1217             if h1 >= 600:
1218                 game.quad[w.i][w.j] = '.'
1219                 deadkl(w, iquad, w)
1220                 game.tholian = None
1221                 return None
1222             skip(1)
1223             proutn(crmena(True, 'T', "sector", w))
1224             if withprob(0.05):
1225                 prout(_(" survives photon blast."))
1226                 return None
1227             prout(_(" disappears."))
1228             game.tholian.move(None)
1229             game.quad[w.i][w.j] = '#'
1230             dropin(' ')
1231             return None
1232         else: # Problem!
1233             skip(1)
1234             proutn("Don't know how to handle torpedo collision with ")
1235             proutn(crmena(True, iquad, "sector", w))
1236             skip(1)
1237             return None
1238         break
1239     skip(1)
1240     prout(_("Torpedo missed."))
1241     return None;
1242
1243 def fry(hit):
1244     "Critical-hit resolution." 
1245     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1246         return
1247     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1248     proutn(_("***CRITICAL HIT--"))
1249     # Select devices and cause damage
1250     cdam = []
1251     for loop1 in range(ncrit):
1252         while True:
1253             j = randdevice()
1254             # Cheat to prevent shuttle damage unless on ship 
1255             if not (game.damage[j]<0.0 or (j==DSHUTTL and game.iscraft != "onship")):
1256                 break
1257         cdam.append(j)
1258         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1259         game.damage[j] += extradm
1260     skipcount = 0
1261     for (i, j) in enumerate(cdam):
1262         proutn(device[j])
1263         if skipcount % 3 == 2 and i < len(cdam)-1:
1264             skip(1)
1265         skipcount += 1
1266         if i < len(cdam)-1:
1267             proutn(_(" and "))
1268     prout(_(" damaged."))
1269     if damaged(DSHIELD) and game.shldup:
1270         prout(_("***Shields knocked down."))
1271         game.shldup=False
1272
1273 def attack(torps_ok):
1274     # bad guy attacks us 
1275     # torps_ok == False forces use of phasers in an attack 
1276     # game could be over at this point, check
1277     if game.alldone:
1278         return
1279     attempt = False; ihurt = False;
1280     hitmax=0.0; hittot=0.0; chgfac=1.0
1281     where = "neither"
1282     if idebug:
1283         prout("=== ATTACK!")
1284     # Tholian gets to move before attacking 
1285     if game.tholian:
1286         movetholian()
1287     # if you have just entered the RNZ, you'll get a warning 
1288     if game.neutz: # The one chance not to be attacked 
1289         game.neutz = False
1290         return
1291     # commanders get a chance to tac-move towards you 
1292     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:
1293         moveklings()
1294     # if no enemies remain after movement, we're done 
1295     if len(game.enemies)==0 or (len(game.enemies)==1 and thing == game.quadrant and not thing.angry):
1296         return
1297     # set up partial hits if attack happens during shield status change 
1298     pfac = 1.0/game.inshld
1299     if game.shldchg:
1300         chgfac = 0.25 + randreal(0.5)
1301     skip(1)
1302     # message verbosity control 
1303     if game.skill <= SKILL_FAIR:
1304         where = "sector"
1305     for enemy in game.enemies:
1306         if enemy.power < 0:
1307             continue;   # too weak to attack 
1308         # compute hit strength and diminish shield power 
1309         r = randreal()
1310         # Increase chance of photon torpedos if docked or enemy energy is low 
1311         if game.condition == "docked":
1312             r *= 0.25
1313         if enemy.power < 500:
1314             r *= 0.25; 
1315         if enemy.type=='T' or (enemy.type=='?' and not thing.angry):
1316             continue
1317         # different enemies have different probabilities of throwing a torp 
1318         usephasers = not torps_ok or \
1319             (enemy.type == 'K' and r > 0.0005) or \
1320             (enemy.type=='C' and r > 0.015) or \
1321             (enemy.type=='R' and r > 0.3) or \
1322             (enemy.type=='S' and r > 0.07) or \
1323             (enemy.type=='?' and r > 0.05)
1324         if usephasers:      # Enemy uses phasers 
1325             if game.condition == "docked":
1326                 continue; # Don't waste the effort! 
1327             attempt = True; # Attempt to attack 
1328             dustfac = randreal(0.8, 0.85)
1329             hit = enemy.power*math.pow(dustfac,enemy.kavgd)
1330             enemy.power *= 0.75
1331         else: # Enemy uses photon torpedo 
1332             # We should be able to make the bearing() method work here
1333             course = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1334             hit = 0
1335             proutn(_("***TORPEDO INCOMING"))
1336             if not damaged(DSRSENS):
1337                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1338             attempt = True
1339             prout("  ")
1340             dispersion = (randreal()+randreal())*0.5 - 0.5
1341             dispersion += 0.002*enemy.power*dispersion
1342             hit = torpedo(enemy.location, course, dispersion, number=1, nburst=1)
1343             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1344                 finish(FWON); # Klingons did themselves in! 
1345             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1346                 return # Supernova or finished 
1347             if hit == None:
1348                 continue
1349         # incoming phaser or torpedo, shields may dissipate it 
1350         if game.shldup or game.shldchg or game.condition=="docked":
1351             # shields will take hits 
1352             propor = pfac * game.shield
1353             if game.condition =="docked":
1354                 propr *= 2.1
1355             if propor < 0.1:
1356                 propor = 0.1
1357             hitsh = propor*chgfac*hit+1.0
1358             absorb = 0.8*hitsh
1359             if absorb > game.shield:
1360                 absorb = game.shield
1361             game.shield -= absorb
1362             hit -= hitsh
1363             # taking a hit blasts us out of a starbase dock 
1364             if game.condition == "docked":
1365                 dock(False)
1366             # but the shields may take care of it 
1367             if propor > 0.1 and hit < 0.005*game.energy:
1368                 continue
1369         # hit from this opponent got through shields, so take damage 
1370         ihurt = True
1371         proutn(_("%d unit hit") % int(hit))
1372         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1373             proutn(_(" on the ") + crmshp())
1374         if not damaged(DSRSENS) and usephasers:
1375             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1376         skip(1)
1377         # Decide if hit is critical 
1378         if hit > hitmax:
1379             hitmax = hit
1380         hittot += hit
1381         fry(hit)
1382         game.energy -= hit
1383     if game.energy <= 0:
1384         # Returning home upon your shield, not with it... 
1385         finish(FBATTLE)
1386         return
1387     if not attempt and game.condition == "docked":
1388         prout(_("***Enemies decide against attacking your ship."))
1389     percent = 100.0*pfac*game.shield+0.5
1390     if not ihurt:
1391         # Shields fully protect ship 
1392         proutn(_("Enemy attack reduces shield strength to "))
1393     else:
1394         # Emit message if starship suffered hit(s) 
1395         skip(1)
1396         proutn(_("Energy left %2d    shields ") % int(game.energy))
1397         if game.shldup:
1398             proutn(_("up "))
1399         elif not damaged(DSHIELD):
1400             proutn(_("down "))
1401         else:
1402             proutn(_("damaged, "))
1403     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1404     # Check if anyone was hurt 
1405     if hitmax >= 200 or hittot >= 500:
1406         icas = randrange(int(hittot * 0.015))
1407         if icas >= 2:
1408             skip(1)
1409             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1410             prout(_("   in that last attack.\""))
1411             game.casual += icas
1412             game.state.crew -= icas
1413     # After attack, reset average distance to enemies 
1414     for enemy in game.enemies:
1415         enemy.kavgd = enemy.kdist
1416     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1417     return
1418                 
1419 def deadkl(w, type, mv):
1420     "Kill a Klingon, Tholian, Romulan, or Thingy." 
1421     # Added mv to allow enemy to "move" before dying 
1422     proutn(crmena(True, type, "sector", mv))
1423     # Decide what kind of enemy it is and update appropriately 
1424     if type == 'R':
1425         # Chalk up a Romulan 
1426         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1427         game.irhere -= 1
1428         game.state.nromrem -= 1
1429     elif type == 'T':
1430         # Killed a Tholian 
1431         game.tholian = None
1432     elif type == '?':
1433         # Killed a Thingy
1434         global thing
1435         thing = None
1436     else:
1437         # Killed some type of Klingon 
1438         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1439         game.klhere -= 1
1440         if type == 'C':
1441             game.state.kcmdr.remove(game.quadrant)
1442             unschedule(FTBEAM)
1443             if game.state.kcmdr:
1444                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1445             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1446                 unschedule(FCDBAS)    
1447         elif type ==  'K':
1448             game.state.remkl -= 1
1449         elif type ==  'S':
1450             game.state.nscrem -= 1
1451             game.state.kscmdr.invalidate()
1452             game.isatb = 0
1453             game.iscate = False
1454             unschedule(FSCMOVE)
1455             unschedule(FSCDBAS)
1456     # For each kind of enemy, finish message to player 
1457     prout(_(" destroyed."))
1458     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1459         return
1460     game.recompute()
1461     # Remove enemy ship from arrays describing local conditions
1462     for e in game.enemies:
1463         if e.location == w:
1464             e.move(None)
1465             break
1466     return
1467
1468 def targetcheck(w):
1469     "Return None if target is invalid, otherwise return a course angle."
1470     if not w.valid_sector():
1471         huh()
1472         return None
1473     delta = coord()
1474     # FIXME: C code this was translated from is wacky -- why the sign reversal?
1475     delta.j = (w.j - game.sector.j);
1476     delta.i = (game.sector.i - w.i);
1477     if delta == coord(0, 0):
1478         skip(1)
1479         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1480         prout(_("  I recommend an immediate review of"))
1481         prout(_("  the Captain's psychological profile.\""))
1482         scanner.chew()
1483         return None
1484     return delta.bearing()
1485
1486 def photon():
1487     "Launch photon torpedo."
1488     course = []
1489     game.ididit = False
1490     if damaged(DPHOTON):
1491         prout(_("Photon tubes damaged."))
1492         scanner.chew()
1493         return
1494     if game.torps == 0:
1495         prout(_("No torpedoes left."))
1496         scanner.chew()
1497         return
1498     # First, get torpedo count
1499     while True:
1500         scanner.next()
1501         if scanner.token == "IHALPHA":
1502             huh()
1503             return
1504         elif scanner.token == "IHEOL" or not scanner.waiting():
1505             prout(_("%d torpedoes left.") % game.torps)
1506             scanner.chew()
1507             proutn(_("Number of torpedoes to fire- "))
1508             continue    # Go back around to get a number
1509         else: # key == "IHREAL"
1510             n = scanner.int()
1511             if n <= 0: # abort command 
1512                 scanner.chew()
1513                 return
1514             if n > MAXBURST:
1515                 scanner.chew()
1516                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1517                 return
1518             if n > game.torps:
1519                 scanner.chew()  # User requested more torps than available
1520                 continue        # Go back around
1521             break       # All is good, go to next stage
1522     # Next, get targets
1523     target = []
1524     for i in range(n):
1525         key = scanner.next()
1526         if i==0 and key == "IHEOL":
1527             break;      # no coordinate waiting, we will try prompting 
1528         if i==1 and key == "IHEOL":
1529             # direct all torpedoes at one target 
1530             while i < n:
1531                 target.append(target[0])
1532                 course.append(course[0])
1533                 i += 1
1534             break
1535         scanner.push(scanner.token)
1536         target.append(scanner.getcoord())
1537         if target[-1] == None:
1538             return
1539         course.append(targetcheck(target[-1]))
1540         if course[-1] == None:
1541             return
1542     scanner.chew()
1543     if len(target) == 0:
1544         # prompt for each one 
1545         for i in range(n):
1546             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1547             scanner.chew()
1548             target.append(scanner.getcoord())
1549             if target[-1] == None:
1550                 return
1551             course.append(targetcheck(target[-1]))
1552             if course[-1] == None:
1553                 return
1554     game.ididit = True
1555     # Loop for moving <n> torpedoes 
1556     for i in range(n):
1557         if game.condition != "docked":
1558             game.torps -= 1
1559         dispersion = (randreal()+randreal())*0.5 -0.5
1560         if math.fabs(dispersion) >= 0.47:
1561             # misfire! 
1562             dispersion *= randreal(1.2, 2.2)
1563             if n > 0:
1564                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1565             else:
1566                 prouts(_("***TORPEDO MISFIRES."))
1567             skip(1)
1568             if i < n:
1569                 prout(_("  Remainder of burst aborted."))
1570             if withprob(0.2):
1571                 prout(_("***Photon tubes damaged by misfire."))
1572                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1573             break
1574         if game.shldup or game.condition == "docked":
1575             dispersion *= 1.0 + 0.0001*game.shield
1576         torpedo(game.sector, course[i], dispersion, number=i, nburst=n)
1577         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1578             return
1579     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1580         finish(FWON);
1581
1582 def overheat(rpow):
1583     "Check for phasers overheating."
1584     if rpow > 1500:
1585         checkburn = (rpow-1500.0)*0.00038
1586         if withprob(checkburn):
1587             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1588             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1589
1590 def checkshctrl(rpow):
1591     "Check shield control."
1592     skip(1)
1593     if withprob(0.998):
1594         prout(_("Shields lowered."))
1595         return False
1596     # Something bad has happened 
1597     prouts(_("***RED ALERT!  RED ALERT!"))
1598     skip(2)
1599     hit = rpow*game.shield/game.inshld
1600     game.energy -= rpow+hit*0.8
1601     game.shield -= hit*0.2
1602     if game.energy <= 0.0:
1603         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1604         skip(1)
1605         stars()
1606         finish(FPHASER)
1607         return True
1608     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1609     skip(2)
1610     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1611     icas = randrange(int(hit*0.012))
1612     skip(1)
1613     fry(0.8*hit)
1614     if icas:
1615         skip(1)
1616         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1617         prout(_("  %d casualties so far.\"") % icas)
1618         game.casual += icas
1619         game.state.crew -= icas
1620     skip(1)
1621     prout(_("Phaser energy dispersed by shields."))
1622     prout(_("Enemy unaffected."))
1623     overheat(rpow)
1624     return True;
1625
1626 def hittem(hits):
1627     "Register a phaser hit on Klingons and Romulans."
1628     nenhr2 = len(game.enemies); kk=0
1629     w = coord()
1630     skip(1)
1631     for (k, wham) in enumerate(hits):
1632         if wham==0:
1633             continue
1634         dustfac = randreal(0.9, 1.0)
1635         hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
1636         kpini = game.enemies[kk].power
1637         kp = math.fabs(kpini)
1638         if PHASEFAC*hit < kp:
1639             kp = PHASEFAC*hit
1640         if game.enemies[kk].power < 0:
1641             game.enemies[kk].power -= -kp
1642         else:
1643             game.enemies[kk].power -= kp
1644         kpow = game.enemies[kk].power
1645         w = game.enemies[kk].location
1646         if hit > 0.005:
1647             if not damaged(DSRSENS):
1648                 boom(w)
1649             proutn(_("%d unit hit on ") % int(hit))
1650         else:
1651             proutn(_("Very small hit on "))
1652         ienm = game.quad[w.i][w.j]
1653         if ienm=='?':
1654             thing.angry = True
1655         proutn(crmena(False, ienm, "sector", w))
1656         skip(1)
1657         if kpow == 0:
1658             deadkl(w, ienm, w)
1659             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1660                 finish(FWON);           
1661             if game.alldone:
1662                 return
1663             kk -= 1     # don't do the increment
1664             continue
1665         else: # decide whether or not to emasculate klingon 
1666             if kpow>0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1667                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1668                 prout(_("   has just lost its firepower.\""))
1669                 game.enemies[kk].power = -kpow
1670         kk += 1
1671     return
1672
1673 def phasers():
1674     "Fire phasers at bad guys."
1675     hits = []
1676     kz = 0; k = 1; irec=0 # Cheating inhibitor 
1677     ifast = False; no = False; itarg = True; msgflag = True; rpow=0
1678     automode = "NOTSET"
1679     key=0
1680     skip(1)
1681     # SR sensors and Computer are needed for automode 
1682     if damaged(DSRSENS) or damaged(DCOMPTR):
1683         itarg = False
1684     if game.condition == "docked":
1685         prout(_("Phasers can't be fired through base shields."))
1686         scanner.chew()
1687         return
1688     if damaged(DPHASER):
1689         prout(_("Phaser control damaged."))
1690         scanner.chew()
1691         return
1692     if game.shldup:
1693         if damaged(DSHCTRL):
1694             prout(_("High speed shield control damaged."))
1695             scanner.chew()
1696             return
1697         if game.energy <= 200.0:
1698             prout(_("Insufficient energy to activate high-speed shield control."))
1699             scanner.chew()
1700             return
1701         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1702         ifast = True
1703     # Original code so convoluted, I re-did it all
1704     # (That was Tom Almy talking about the C code, I think -- ESR)
1705     while automode=="NOTSET":
1706         key=scanner.next()
1707         if key == "IHALPHA":
1708             if scanner.sees("manual"):
1709                 if len(game.enemies)==0:
1710                     prout(_("There is no enemy present to select."))
1711                     scanner.chew()
1712                     key = "IHEOL"
1713                     automode="AUTOMATIC"
1714                 else:
1715                     automode = "MANUAL"
1716                     key = scanner.next()
1717             elif scanner.sees("automatic"):
1718                 if (not itarg) and len(game.enemies) != 0:
1719                     automode = "FORCEMAN"
1720                 else:
1721                     if len(game.enemies)==0:
1722                         prout(_("Energy will be expended into space."))
1723                     automode = "AUTOMATIC"
1724                     key = scanner.next()
1725             elif scanner.sees("no"):
1726                 no = True
1727             else:
1728                 huh()
1729                 return
1730         elif key == "IHREAL":
1731             if len(game.enemies)==0:
1732                 prout(_("Energy will be expended into space."))
1733                 automode = "AUTOMATIC"
1734             elif not itarg:
1735                 automode = "FORCEMAN"
1736             else:
1737                 automode = "AUTOMATIC"
1738         else:
1739             # "IHEOL" 
1740             if len(game.enemies)==0:
1741                 prout(_("Energy will be expended into space."))
1742                 automode = "AUTOMATIC"
1743             elif not itarg:
1744                 automode = "FORCEMAN"
1745             else: 
1746                 proutn(_("Manual or automatic? "))
1747                 scanner.chew()
1748     avail = game.energy
1749     if ifast:
1750         avail -= 200.0
1751     if automode == "AUTOMATIC":
1752         if key == "IHALPHA" and scanner.sees("no"):
1753             no = True
1754             key = scanner.next()
1755         if key != "IHREAL" and len(game.enemies) != 0:
1756             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1757         irec=0
1758         while True:
1759             scanner.chew()
1760             if not kz:
1761                 for i in range(len(game.enemies)):
1762                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1763             kz=1
1764             proutn(_("%d units required. ") % irec)
1765             scanner.chew()
1766             proutn(_("Units to fire= "))
1767             key = scanner.next()
1768             if key!="IHREAL":
1769                 return
1770             rpow = scanner.real
1771             if rpow > avail:
1772                 proutn(_("Energy available= %.2f") % avail)
1773                 skip(1)
1774                 key = "IHEOL"
1775             if not rpow > avail:
1776                 break
1777         if rpow<=0:
1778             # chicken out 
1779             scanner.chew()
1780             return
1781         key=scanner.next()
1782         if key == "IHALPHA" and scanner.sees("no"):
1783             no = True
1784         if ifast:
1785             game.energy -= 200; # Go and do it! 
1786             if checkshctrl(rpow):
1787                 return
1788         scanner.chew()
1789         game.energy -= rpow
1790         extra = rpow
1791         if len(game.enemies):
1792             extra = 0.0
1793             powrem = rpow
1794             for i in range(len(game.enemies)):
1795                 hits.append(0.0)
1796                 if powrem <= 0:
1797                     continue
1798                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
1799                 over = randreal(1.01, 1.06) * hits[i]
1800                 temp = powrem
1801                 powrem -= hits[i] + over
1802                 if powrem <= 0 and temp < hits[i]:
1803                     hits[i] = temp
1804                 if powrem <= 0:
1805                     over = 0.0
1806                 extra += over
1807             if powrem > 0.0:
1808                 extra += powrem
1809             hittem(hits)
1810             game.ididit = True
1811         if extra > 0 and not game.alldone:
1812             if game.tholian:
1813                 proutn(_("*** Tholian web absorbs "))
1814                 if len(game.enemies)>0:
1815                     proutn(_("excess "))
1816                 prout(_("phaser energy."))
1817             else:
1818                 prout(_("%d expended on empty space.") % int(extra))
1819     elif automode == "FORCEMAN":
1820         scanner.chew()
1821         key = "IHEOL"
1822         if damaged(DCOMPTR):
1823             prout(_("Battle computer damaged, manual fire only."))
1824         else:
1825             skip(1)
1826             prouts(_("---WORKING---"))
1827             skip(1)
1828             prout(_("Short-range-sensors-damaged"))
1829             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1830             prout(_("Manual-fire-must-be-used"))
1831             skip(1)
1832     elif automode == "MANUAL":
1833         rpow = 0.0
1834         for k in range(len(game.enemies)):
1835             aim = game.enemies[k].location
1836             ienm = game.quad[aim.i][aim.j]
1837             if msgflag:
1838                 proutn(_("Energy available= %.2f") % (avail-0.006))
1839                 skip(1)
1840                 msgflag = False
1841                 rpow = 0.0
1842             if damaged(DSRSENS) and \
1843                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
1844                 prout(cramen(ienm) + _(" can't be located without short range scan."))
1845                 scanner.chew()
1846                 key = "IHEOL"
1847                 hits[k] = 0; # prevent overflow -- thanks to Alexei Voitenko 
1848                 k += 1
1849                 continue
1850             if key == "IHEOL":
1851                 scanner.chew()
1852                 if itarg and k > kz:
1853                     irec=(abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) *  randreal(1.01, 1.06) + 1.0
1854                 kz = k
1855                 proutn("(")
1856                 if not damaged(DCOMPTR):
1857                     proutn("%d" % irec)
1858                 else:
1859                     proutn("??")
1860                 proutn(")  ")
1861                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))                
1862                 key = scanner.next()
1863             if key == "IHALPHA" and scanner.sees("no"):
1864                 no = True
1865                 key = scanner.next()
1866                 continue
1867             if key == "IHALPHA":
1868                 huh()
1869                 return
1870             if key == "IHEOL":
1871                 if k==1: # Let me say I'm baffled by this 
1872                     msgflag = True
1873                 continue
1874             if scanner.real < 0:
1875                 # abort out 
1876                 scanner.chew()
1877                 return
1878             hits[k] = scanner.real
1879             rpow += scanner.real
1880             # If total requested is too much, inform and start over 
1881             if rpow > avail:
1882                 prout(_("Available energy exceeded -- try again."))
1883                 scanner.chew()
1884                 return
1885             key = scanner.next(); # scan for next value 
1886             k += 1
1887         if rpow == 0.0:
1888             # zero energy -- abort 
1889             scanner.chew()
1890             return
1891         if key == "IHALPHA" and scanner.sees("no"):
1892             no = True
1893         game.energy -= rpow
1894         scanner.chew()
1895         if ifast:
1896             game.energy -= 200.0
1897             if checkshctrl(rpow):
1898                 return
1899         hittem(hits)
1900         game.ididit = True
1901      # Say shield raised or malfunction, if necessary 
1902     if game.alldone:
1903         return
1904     if ifast:
1905         skip(1)
1906         if no == 0:
1907             if withprob(0.01):
1908                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
1909                 prouts(_("         CLICK   CLICK   POP  . . ."))
1910                 prout(_(" No response, sir!"))
1911                 game.shldup = False
1912             else:
1913                 prout(_("Shields raised."))
1914         else:
1915             game.shldup = False
1916     overheat(rpow);
1917
1918 # Code from events,c begins here.
1919
1920 # This isn't a real event queue a la BSD Trek yet -- you can only have one 
1921 # event of each type active at any given time.  Mostly these means we can 
1922 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
1923 # BSD Trek, from which we swiped the idea, can have up to 5.
1924
1925 def unschedule(evtype):
1926     "Remove an event from the schedule."
1927     game.future[evtype].date = FOREVER
1928     return game.future[evtype]
1929
1930 def is_scheduled(evtype):
1931     "Is an event of specified type scheduled."
1932     return game.future[evtype].date != FOREVER
1933
1934 def scheduled(evtype):
1935     "When will this event happen?"
1936     return game.future[evtype].date
1937
1938 def schedule(evtype, offset):
1939     "Schedule an event of specified type."
1940     game.future[evtype].date = game.state.date + offset
1941     return game.future[evtype]
1942
1943 def postpone(evtype, offset):
1944     "Postpone a scheduled event."
1945     game.future[evtype].date += offset
1946
1947 def cancelrest():
1948     "Rest period is interrupted by event."
1949     if game.resting:
1950         skip(1)
1951         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
1952         if ja() == True:
1953             game.resting = False
1954             game.optime = 0.0
1955             return True
1956     return False
1957
1958 def events():
1959     "Run through the event queue looking for things to do."
1960     i=0
1961     fintim = game.state.date + game.optime; yank=0
1962     ictbeam = False; istract = False
1963     w = coord(); hold = coord()
1964     ev = event(); ev2 = event()
1965
1966     def tractorbeam(yank):
1967         "Tractor-beaming cases merge here." 
1968         announce()
1969         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5) 
1970         skip(1)
1971         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
1972         # If Kirk & Co. screwing around on planet, handle 
1973         atover(True) # atover(true) is Grab 
1974         if game.alldone:
1975             return
1976         if game.icraft: # Caught in Galileo? 
1977             finish(FSTRACTOR)
1978             return
1979         # Check to see if shuttle is aboard 
1980         if game.iscraft == "offship":
1981             skip(1)
1982             if withprob(0.5):
1983                 prout(_("Galileo, left on the planet surface, is captured"))
1984                 prout(_("by aliens and made into a flying McDonald's."))
1985                 game.damage[DSHUTTL] = -10
1986                 game.iscraft = "removed"
1987             else:
1988                 prout(_("Galileo, left on the planet surface, is well hidden."))
1989         if evcode == FSPY:
1990             game.quadrant = game.state.kscmdr
1991         else:
1992             game.quadrant = game.state.kcmdr[i]
1993         game.sector = randplace(QUADSIZE)
1994         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
1995                % (game.quadrant, game.sector))
1996         if game.resting:
1997             prout(_("(Remainder of rest/repair period cancelled.)"))
1998             game.resting = False
1999         if not game.shldup:
2000             if not damaged(DSHIELD) and game.shield > 0:
2001                 doshield(shraise=True) # raise shields 
2002                 game.shldchg = False
2003             else:
2004                 prout(_("(Shields not currently useable.)"))
2005         newqad()
2006         # Adjust finish time to time of tractor beaming 
2007         fintim = game.state.date+game.optime
2008         attack(torps_ok=False)
2009         if not game.state.kcmdr:
2010             unschedule(FTBEAM)
2011         else: 
2012             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2013
2014     def destroybase():
2015         "Code merges here for any commander destroying a starbase." 
2016         # Not perfect, but will have to do 
2017         # Handle case where base is in same quadrant as starship 
2018         if game.battle == game.quadrant:
2019             game.state.chart[game.battle.i][game.battle.j].starbase = False
2020             game.quad[game.base.i][game.base.j] = '.'
2021             game.base.invalidate()
2022             newcnd()
2023             skip(1)
2024             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2025         elif game.state.baseq and communicating():
2026             # Get word via subspace radio 
2027             announce()
2028             skip(1)
2029             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2030             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2031             if game.isatb == 2: 
2032                 prout(_("the Klingon Super-Commander"))
2033             else:
2034                 prout(_("a Klingon Commander"))
2035             game.state.chart[game.battle.i][game.battle.j].starbase = False
2036         # Remove Starbase from galaxy 
2037         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2038         game.state.baseq = filter(lambda x: x != game.battle, game.state.baseq)
2039         if game.isatb == 2:
2040             # reinstate a commander's base attack 
2041             game.battle = hold
2042             game.isatb = 0
2043         else:
2044             game.battle.invalidate()
2045     if idebug:
2046         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2047         for i in range(1, NEVENTS):
2048             if   i == FSNOVA:  proutn("=== Supernova       ")
2049             elif i == FTBEAM:  proutn("=== T Beam          ")
2050             elif i == FSNAP:   proutn("=== Snapshot        ")
2051             elif i == FBATTAK: proutn("=== Base Attack     ")
2052             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2053             elif i == FSCMOVE: proutn("=== SC Move         ")
2054             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2055             elif i == FDSPROB: proutn("=== Probe Move      ")
2056             elif i == FDISTR:  proutn("=== Distress Call   ")
2057             elif i == FENSLV:  proutn("=== Enslavement     ")
2058             elif i == FREPRO:  proutn("=== Klingon Build   ")
2059             if is_scheduled(i):
2060                 prout("%.2f" % (scheduled(i)))
2061             else:
2062                 prout("never")
2063     radio_was_broken = damaged(DRADIO)
2064     hold.i = hold.j = 0
2065     while True:
2066         # Select earliest extraneous event, evcode==0 if no events 
2067         evcode = FSPY
2068         if game.alldone:
2069             return
2070         datemin = fintim
2071         for l in range(1, NEVENTS):
2072             if game.future[l].date < datemin:
2073                 evcode = l
2074                 if idebug:
2075                     prout("== Event %d fires" % evcode)
2076                 datemin = game.future[l].date
2077         xtime = datemin-game.state.date
2078         game.state.date = datemin
2079         # Decrement Federation resources and recompute remaining time 
2080         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2081         game.recompute()
2082         if game.state.remtime <=0:
2083             finish(FDEPLETE)
2084             return
2085         # Any crew left alive? 
2086         if game.state.crew <=0:
2087             finish(FCREW)
2088             return
2089         # Is life support adequate? 
2090         if damaged(DLIFSUP) and game.condition != "docked":
2091             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2092                 finish(FLIFESUP)
2093                 return
2094             game.lsupres -= xtime
2095             if game.damage[DLIFSUP] <= xtime:
2096                 game.lsupres = game.inlsr
2097         # Fix devices 
2098         repair = xtime
2099         if game.condition == "docked":
2100             repair /= DOCKFAC
2101         # Don't fix Deathray here 
2102         for l in range(NDEVICES):
2103             if game.damage[l] > 0.0 and l != DDRAY:
2104                 if game.damage[l]-repair > 0.0:
2105                     game.damage[l] -= repair
2106                 else:
2107                     game.damage[l] = 0.0
2108         # If radio repaired, update star chart and attack reports 
2109         if radio_was_broken and not damaged(DRADIO):
2110             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2111             prout(_("   surveillance reports are coming in."))
2112             skip(1)
2113             if not game.iseenit:
2114                 attackreport(False)
2115                 game.iseenit = True
2116             rechart()
2117             prout(_("   The star chart is now up to date.\""))
2118             skip(1)
2119         # Cause extraneous event EVCODE to occur 
2120         game.optime -= xtime
2121         if evcode == FSNOVA: # Supernova 
2122             announce()
2123             supernova(None)
2124             schedule(FSNOVA, expran(0.5*game.intime))
2125             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2126                 return
2127         elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
2128             if game.state.nscrem == 0 or \
2129                 ictbeam or istract or \
2130                 game.condition=="docked" or game.isatb==1 or game.iscate:
2131                 return
2132             if game.ientesc or \
2133                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2134                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2135                 (damaged(DSHIELD) and \
2136                  (game.energy < 2500 or damaged(DPHASER)) and \
2137                  (game.torps < 5 or damaged(DPHOTON))):
2138                 # Tractor-beam her! 
2139                 istract = ictbeam = True
2140                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2141             else:
2142                 return
2143         elif evcode == FTBEAM: # Tractor beam 
2144             if not game.state.kcmdr:
2145                 unschedule(FTBEAM)
2146                 continue
2147             i = randrange(len(game.state.kcmdr))
2148             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2149             if istract or game.condition == "docked" or yank == 0:
2150                 # Drats! Have to reschedule 
2151                 schedule(FTBEAM, 
2152                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2153                 continue
2154             ictbeam = True
2155             tractorbeam(yank)
2156         elif evcode == FSNAP: # Snapshot of the universe (for time warp) 
2157             game.snapsht = copy.deepcopy(game.state)
2158             game.state.snap = True
2159             schedule(FSNAP, expran(0.5 * game.intime))
2160         elif evcode == FBATTAK: # Commander attacks starbase 
2161             if not game.state.kcmdr or not game.state.baseq:
2162                 # no can do 
2163                 unschedule(FBATTAK)
2164                 unschedule(FCDBAS)
2165                 continue
2166             try:
2167                 for ibq in game.state.baseq:
2168                    for cmdr in game.state.kcmdr: 
2169                        if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2170                            raise ibq
2171                 else:
2172                     # no match found -- try later 
2173                     schedule(FBATTAK, expran(0.3*game.intime))
2174                     unschedule(FCDBAS)
2175                     continue
2176             except coord:
2177                 pass
2178             # commander + starbase combination found -- launch attack 
2179             game.battle = ibq
2180             schedule(FCDBAS, randreal(1.0, 4.0))
2181             if game.isatb: # extra time if SC already attacking 
2182                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2183             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2184             game.iseenit = False
2185             if not communicating():
2186                 continue # No warning :-( 
2187             game.iseenit = True
2188             announce()
2189             skip(1)
2190             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2191             prout(_("   reports that it is under attack and that it can"))
2192             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2193             if cancelrest():
2194                 return
2195         elif evcode == FSCDBAS: # Supercommander destroys base 
2196             unschedule(FSCDBAS)
2197             game.isatb = 2
2198             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase: 
2199                 continue # WAS RETURN! 
2200             hold = game.battle
2201             game.battle = game.state.kscmdr
2202             destroybase()
2203         elif evcode == FCDBAS: # Commander succeeds in destroying base 
2204             if evcode==FCDBAS:
2205                 unschedule(FCDBAS)
2206                 if not game.state.baseq() \
2207                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2208                     game.battle.invalidate()
2209                     continue
2210                 # find the lucky pair 
2211                 for cmdr in game.state.kcmdr:
2212                     if cmdr == game.battle: 
2213                         break
2214                 else:
2215                     # No action to take after all 
2216                     continue
2217             destroybase()
2218         elif evcode == FSCMOVE: # Supercommander moves 
2219             schedule(FSCMOVE, 0.2777)
2220             if not game.ientesc and not istract and game.isatb != 1 and \
2221                    (not game.iscate or not game.justin): 
2222                 supercommander()
2223         elif evcode == FDSPROB: # Move deep space probe 
2224             schedule(FDSPROB, 0.01)
2225             if not game.probe.next():
2226                 if not game.probe.quadrant().valid_quadrant() or \
2227                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2228                     # Left galaxy or ran into supernova
2229                     if communicating():
2230                         announce()
2231                         skip(1)
2232                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2233                         if not game.probe.quadrant().valid_quadrant():
2234                             prout(_("has left the galaxy.\""))
2235                         else:
2236                             prout(_("is no longer transmitting.\""))
2237                     unschedule(FDSPROB)
2238                     continue
2239                 if communicating():
2240                     #announce()
2241                     skip(1)
2242                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2243             pdest = game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j]
2244             if communicating():
2245                 chp = game.state.chart[game.probe.quadrant().i][game.probe.quadrant().j]
2246                 chp.klingons = pdest.klingons
2247                 chp.starbase = pdest.starbase
2248                 chp.stars = pdest.stars
2249                 pdest.charted = True
2250             game.probe.moves -= 1 # One less to travel
2251             if game.probe.arrived() and game.isarmed and pdest.stars:
2252                 supernova(game.probe)           # fire in the hole!
2253                 unschedule(FDSPROB)
2254                 if game.state.galaxy[game.quadrant().i][game.quadrant().j].supernova: 
2255                     return
2256         elif evcode == FDISTR: # inhabited system issues distress call 
2257             unschedule(FDISTR)
2258             # try a whole bunch of times to find something suitable 
2259             for i in range(100):
2260                 # need a quadrant which is not the current one,
2261                 # which has some stars which are inhabited and
2262                 # not already under attack, which is not
2263                 # supernova'ed, and which has some Klingons in it
2264                 w = randplace(GALSIZE)
2265                 q = game.state.galaxy[w.i][w.j]
2266                 if not (game.quadrant == w or q.planet == None or \
2267                       not q.planet.inhabited or \
2268                       q.supernova or q.status!="secure" or q.klingons<=0):
2269                     break
2270             else:
2271                 # can't seem to find one; ignore this call 
2272                 if idebug:
2273                     prout("=== Couldn't find location for distress event.")
2274                 continue
2275             # got one!!  Schedule its enslavement 
2276             ev = schedule(FENSLV, expran(game.intime))
2277             ev.quadrant = w
2278             q.status = "distressed"
2279             # tell the captain about it if we can 
2280             if communicating():
2281                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2282                         % (q.planet, `w`))
2283                 prout(_("by a Klingon invasion fleet."))
2284                 if cancelrest():
2285                     return
2286         elif evcode == FENSLV:          # starsystem is enslaved 
2287             ev = unschedule(FENSLV)
2288             # see if current distress call still active 
2289             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2290             if q.klingons <= 0:
2291                 q.status = "secure"
2292                 continue
2293             q.status = "enslaved"
2294
2295             # play stork and schedule the first baby 
2296             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2297             ev2.quadrant = ev.quadrant
2298
2299             # report the disaster if we can 
2300             if communicating():
2301                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2302                         q.planet)
2303                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2304         elif evcode == FREPRO:          # Klingon reproduces 
2305             # If we ever switch to a real event queue, we'll need to
2306             # explicitly retrieve and restore the x and y.
2307             ev = schedule(FREPRO, expran(1.0 * game.intime))
2308             # see if current distress call still active 
2309             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2310             if q.klingons <= 0:
2311                 q.status = "secure"
2312                 continue
2313             if game.state.remkl >=MAXKLGAME:
2314                 continue                # full right now 
2315             # reproduce one Klingon 
2316             w = ev.quadrant
2317             m = coord()
2318             if game.klhere >= MAXKLQUAD:
2319                 try:
2320                     # this quadrant not ok, pick an adjacent one 
2321                     for m.i in range(w.i - 1, w.i + 2):
2322                         for m.j in range(w.j - 1, w.j + 2):
2323                             if not m.valid_quadrant():
2324                                 continue
2325                             q = game.state.galaxy[m.i][m.j]
2326                             # check for this quad ok (not full & no snova) 
2327                             if q.klingons >= MAXKLQUAD or q.supernova:
2328                                 continue
2329                             raise "FOUNDIT"
2330                     else:
2331                         continue        # search for eligible quadrant failed
2332                 except "FOUNDIT":
2333                     w = m
2334             # deliver the child 
2335             game.state.remkl += 1
2336             q.klingons += 1
2337             if game.quadrant == w:
2338                 game.klhere += 1
2339                 game.enemies.append(newkling())
2340             # recompute time left
2341             game.recompute()
2342             if communicating():
2343                 if game.quadrant == w:
2344                     prout(_("Spock- sensors indicate the Klingons have"))
2345                     prout(_("launched a warship from %s.") % q.planet)
2346                 else:
2347                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2348                     if q.planet != None:
2349                         proutn(_("near %s ") % q.planet)
2350                     prout(_("in Quadrant %s.") % w)
2351                                 
2352 def wait():
2353     "Wait on events."
2354     game.ididit = False
2355     while True:
2356         key = scanner.next()
2357         if key  != "IHEOL":
2358             break
2359         proutn(_("How long? "))
2360     scanner.chew()
2361     if key != "IHREAL":
2362         huh()
2363         return
2364     origTime = delay = scanner.real
2365     if delay <= 0.0:
2366         return
2367     if delay >= game.state.remtime or len(game.enemies) != 0:
2368         proutn(_("Are you sure? "))
2369         if ja() == False:
2370             return
2371     # Alternate resting periods (events) with attacks 
2372     game.resting = True
2373     while True:
2374         if delay <= 0:
2375             game.resting = False
2376         if not game.resting:
2377             prout(_("%d stardates left.") % int(game.state.remtime))
2378             return
2379         temp = game.optime = delay
2380         if len(game.enemies):
2381             rtime = randreal(1.0, 2.0)
2382             if rtime < temp:
2383                 temp = rtime
2384             game.optime = temp
2385         if game.optime < delay:
2386             attack(torps_ok=False)
2387         if game.alldone:
2388             return
2389         events()
2390         game.ididit = True
2391         if game.alldone:
2392             return
2393         delay -= temp
2394         # Repair Deathray if long rest at starbase 
2395         if origTime-delay >= 9.99 and game.condition == "docked":
2396             game.damage[DDRAY] = 0.0
2397         # leave if quadrant supernovas
2398         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2399             break
2400     game.resting = False
2401     game.optime = 0
2402
2403 def nova(nov):
2404     "Star goes nova." 
2405     course = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2406     newc = coord(); neighbor = coord(); bump = coord(0, 0)
2407     if withprob(0.05):
2408         # Wow! We've supernova'ed 
2409         supernova(game.quadrant)
2410         return
2411     # handle initial nova 
2412     game.quad[nov.i][nov.j] = '.'
2413     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2414     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2415     game.state.starkl += 1
2416     # Set up queue to recursively trigger adjacent stars 
2417     hits = [nov]
2418     kount = 0
2419     while hits:
2420         offset = coord()
2421         start = hits.pop()
2422         for offset.i in range(-1, 1+1):
2423             for offset.j in range(-1, 1+1):
2424                 if offset.j==0 and offset.i==0:
2425                     continue
2426                 neighbor = start + offset
2427                 if not neighbor.valid_sector():
2428                     continue
2429                 iquad = game.quad[neighbor.i][neighbor.j]
2430                 # Empty space ends reaction
2431                 if iquad in ('.', '?', ' ', 'T', '#'):
2432                     pass
2433                 elif iquad == '*': # Affect another star 
2434                     if withprob(0.05):
2435                         # This star supernovas 
2436                         supernova(game.quadrant)
2437                         return
2438                     else:
2439                         hits.append(neighbor)
2440                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2441                         game.state.starkl += 1
2442                         proutn(crmena(True, '*', "sector", neighbor))
2443                         prout(_(" novas."))
2444                         game.quad[neighbor.i][neighbor.j] = '.'
2445                         kount += 1
2446                 elif iquad in ('P', '@'): # Destroy planet 
2447                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2448                     if iquad == 'P':
2449                         game.state.nplankl += 1
2450                     else:
2451                         game.state.worldkl += 1
2452                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2453                     game.iplnet.pclass = "destroyed"
2454                     game.iplnet = None
2455                     game.plnet.invalidate()
2456                     if game.landed:
2457                         finish(FPNOVA)
2458                         return
2459                     game.quad[neighbor.i][neighbor.j] = '.'
2460                 elif iquad == 'B': # Destroy base 
2461                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2462                     game.state.baseq = filter(lambda x: x!= game.quadrant, game.state.baseq)
2463                     game.base.invalidate()
2464                     game.state.basekl += 1
2465                     newcnd()
2466                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2467                     game.quad[neighbor.i][neighbor.j] = '.'
2468                 elif iquad in ('E', 'F'): # Buffet ship 
2469                     prout(_("***Starship buffeted by nova."))
2470                     if game.shldup:
2471                         if game.shield >= 2000.0:
2472                             game.shield -= 2000.0
2473                         else:
2474                             diff = 2000.0 - game.shield
2475                             game.energy -= diff
2476                             game.shield = 0.0
2477                             game.shldup = False
2478                             prout(_("***Shields knocked out."))
2479                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2480                     else:
2481                         game.energy -= 2000.0
2482                     if game.energy <= 0:
2483                         finish(FNOVA)
2484                         return
2485                     # add in course nova contributes to kicking starship
2486                     bump += (game.sector-hits[mm]).sgn()
2487                 elif iquad == 'K': # kill klingon 
2488                     deadkl(neighbor, iquad, neighbor)
2489                 elif iquad in ('C','S','R'): # Damage/destroy big enemies 
2490                     for ll in range(len(game.enemies)):
2491                         if game.enemies[ll].location == neighbor:
2492                             break
2493                     game.enemies[ll].power -= 800.0 # If firepower is lost, die 
2494                     if game.enemies[ll].power <= 0.0:
2495                         deadkl(neighbor, iquad, neighbor)
2496                         break
2497                     newc = neighbor + neighbor - hits[mm]
2498                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2499                     if not newc.valid_sector():
2500                         # can't leave quadrant 
2501                         skip(1)
2502                         break
2503                     iquad1 = game.quad[newc.i][newc.j]
2504                     if iquad1 == ' ':
2505                         proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2506                         skip(1)
2507                         deadkl(neighbor, iquad, newc)
2508                         break
2509                     if iquad1 != '.':
2510                         # can't move into something else 
2511                         skip(1)
2512                         break
2513                     proutn(_(", buffeted to Sector %s") % newc)
2514                     game.quad[neighbor.i][neighbor.j] = '.'
2515                     game.quad[newc.i][newc.j] = iquad
2516                     game.enemies[ll].move(newc)
2517     # Starship affected by nova -- kick it away. 
2518     dist = kount*0.1
2519     direc = course[3*(bump.i+1)+bump.j+2]
2520     if direc == 0.0:
2521         dist = 0.0
2522     if dist == 0.0:
2523         return
2524     course = course(bearing=direc, distance=dist)
2525     game.optime = course.time(warp=4)
2526     skip(1)
2527     prout(_("Force of nova displaces starship."))
2528     imove(course, noattack=True)
2529     game.optime = course.time(warp=4)
2530     return
2531         
2532 def supernova(w):
2533     "Star goes supernova."
2534     num = 0; npdead = 0
2535     if w != None: 
2536         nq = copy.copy(w)
2537     else:
2538         # Scheduled supernova -- select star at random. 
2539         stars = 0
2540         nq = coord()
2541         for nq.i in range(GALSIZE):
2542             for nq.j in range(GALSIZE):
2543                 stars += game.state.galaxy[nq.i][nq.j].stars
2544         if stars == 0:
2545             return # nothing to supernova exists 
2546         num = randrange(stars) + 1
2547         for nq.i in range(GALSIZE):
2548             for nq.j in range(GALSIZE):
2549                 num -= game.state.galaxy[nq.i][nq.j].stars
2550                 if num <= 0:
2551                     break
2552             if num <=0:
2553                 break
2554         if idebug:
2555             proutn("=== Super nova here?")
2556             if ja() == True:
2557                 nq = game.quadrant
2558     if not nq == game.quadrant or game.justin:
2559         # it isn't here, or we just entered (treat as enroute) 
2560         if communicating():
2561             skip(1)
2562             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2563             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2564     else:
2565         ns = coord()
2566         # we are in the quadrant! 
2567         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2568         for ns.i in range(QUADSIZE):
2569             for ns.j in range(QUADSIZE):
2570                 if game.quad[ns.i][ns.j]=='*':
2571                     num -= 1
2572                     if num==0:
2573                         break
2574             if num==0:
2575                 break
2576         skip(1)
2577         prouts(_("***RED ALERT!  RED ALERT!"))
2578         skip(1)
2579         prout(_("***Incipient supernova detected at Sector %s") % ns)
2580         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2581             proutn(_("Emergency override attempts t"))
2582             prouts("***************")
2583             skip(1)
2584             stars()
2585             game.alldone = True
2586     # destroy any Klingons in supernovaed quadrant
2587     kldead = game.state.galaxy[nq.i][nq.j].klingons
2588     game.state.galaxy[nq.i][nq.j].klingons = 0
2589     if nq == game.state.kscmdr:
2590         # did in the Supercommander! 
2591         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2592         game.iscate = False
2593         unschedule(FSCMOVE)
2594         unschedule(FSCDBAS)
2595     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2596     comkills = len(game.state.kcmdr) - len(survivors)
2597     game.state.kcmdr = survivors
2598     kldead -= comkills
2599     if not game.state.kcmdr:
2600         unschedule(FTBEAM)
2601     game.state.remkl -= kldead
2602     # destroy Romulans and planets in supernovaed quadrant 
2603     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2604     game.state.galaxy[nq.i][nq.j].romulans = 0
2605     game.state.nromrem -= nrmdead
2606     # Destroy planets 
2607     for loop in range(game.inplan):
2608         if game.state.planets[loop].quadrant == nq:
2609             game.state.planets[loop].pclass = "destroyed"
2610             npdead += 1
2611     # Destroy any base in supernovaed quadrant
2612     game.state.baseq = filter(lambda x: x != nq, game.state.baseq)
2613     # If starship caused supernova, tally up destruction 
2614     if w != None:
2615         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2616         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2617         game.state.nplankl += npdead
2618     # mark supernova in galaxy and in star chart 
2619     if game.quadrant == nq or communicating():
2620         game.state.galaxy[nq.i][nq.j].supernova = True
2621     # If supernova destroys last Klingons give special message 
2622     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2623         skip(2)
2624         if w == None:
2625             prout(_("Lucky you!"))
2626         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2627         finish(FWON)
2628         return
2629     # if some Klingons remain, continue or die in supernova 
2630     if game.alldone:
2631         finish(FSNOVAED)
2632     return
2633
2634 # Code from finish.c ends here.
2635
2636 def selfdestruct():
2637     "Self-destruct maneuver. Finish with a BANG!" 
2638     scanner.chew()
2639     if damaged(DCOMPTR):
2640         prout(_("Computer damaged; cannot execute destruct sequence."))
2641         return
2642     prouts(_("---WORKING---")); skip(1)
2643     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2644     prouts("   10"); skip(1)
2645     prouts("       9"); skip(1)
2646     prouts("          8"); skip(1)
2647     prouts("             7"); skip(1)
2648     prouts("                6"); skip(1)
2649     skip(1)
2650     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2651     skip(1)
2652     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2653     skip(1)
2654     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2655     skip(1)
2656     scanner.next()
2657     scanner.chew()
2658     if game.passwd != scanner.token:
2659         prouts(_("PASSWORD-REJECTED;"))
2660         skip(1)
2661         prouts(_("CONTINUITY-EFFECTED"))
2662         skip(2)
2663         return
2664     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2665     prouts("                   5"); skip(1)
2666     prouts("                      4"); skip(1)
2667     prouts("                         3"); skip(1)
2668     prouts("                            2"); skip(1)
2669     prouts("                              1"); skip(1)
2670     if withprob(0.15):
2671         prouts(_("GOODBYE-CRUEL-WORLD"))
2672         skip(1)
2673     kaboom()
2674
2675 def kaboom():
2676     stars()
2677     if game.ship=='E':
2678         prouts("***")
2679     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2680     skip(1)
2681     stars()
2682     skip(1)
2683     if len(game.enemies) != 0:
2684         whammo = 25.0 * game.energy
2685         l=1
2686         while l <= len(game.enemies):
2687             if game.enemies[l].power*game.enemies[l].kdist <= whammo: 
2688                 deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
2689             l += 1
2690     finish(FDILITHIUM)
2691                                 
2692 def killrate():
2693     "Compute our rate of kils over time."
2694     elapsed = game.state.date - game.indate
2695     if elapsed == 0:    # Avoid divide-by-zero error if calculated on turn 0
2696         return 0
2697     else:
2698         starting = (game.inkling + game.incom + game.inscom)
2699         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2700         return (starting - remaining)/elapsed
2701
2702 def badpoints():
2703     "Compute demerits."
2704     badpt = 5.0*game.state.starkl + \
2705             game.casual + \
2706             10.0*game.state.nplankl + \
2707             300*game.state.nworldkl + \
2708             45.0*game.nhelp +\
2709             100.0*game.state.basekl +\
2710             3.0*game.abandoned
2711     if game.ship == 'F':
2712         badpt += 100.0
2713     elif game.ship == None:
2714         badpt += 200.0
2715     return badpt
2716
2717 def finish(ifin):
2718     # end the game, with appropriate notfications 
2719     igotit = False
2720     game.alldone = True
2721     skip(3)
2722     prout(_("It is stardate %.1f.") % game.state.date)
2723     skip(1)
2724     if ifin == FWON: # Game has been won
2725         if game.state.nromrem != 0:
2726             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2727                   game.state.nromrem)
2728
2729         prout(_("You have smashed the Klingon invasion fleet and saved"))
2730         prout(_("the Federation."))
2731         game.gamewon = True
2732         if game.alive:
2733             badpt = badpoints()
2734             if badpt < 100.0:
2735                 badpt = 0.0     # Close enough!
2736             # killsPerDate >= RateMax
2737             if game.state.date-game.indate < 5.0 or \
2738                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2739                 skip(1)
2740                 prout(_("In fact, you have done so well that Starfleet Command"))
2741                 if game.skill == SKILL_NOVICE:
2742                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2743                 elif game.skill == SKILL_FAIR:
2744                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2745                 elif game.skill == SKILL_GOOD:
2746                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2747                 elif game.skill == SKILL_EXPERT:
2748                     prout(_("promotes you to Commodore Emeritus."))
2749                     skip(1)
2750                     prout(_("Now that you think you're really good, try playing"))
2751                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2752                 elif game.skill == SKILL_EMERITUS:
2753                     skip(1)
2754                     proutn(_("Computer-  "))
2755                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2756                     skip(2)
2757                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2758                     skip(1)
2759                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2760                     skip(1)
2761                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2762                     skip(1)
2763                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2764                     skip(1)
2765                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2766                     skip(2)
2767                     prout(_("Now you can retire and write your own Star Trek game!"))
2768                     skip(1)
2769                 elif game.skill >= SKILL_EXPERT:
2770                     if game.thawed and not idebug:
2771                         prout(_("You cannot get a citation, so..."))
2772                     else:
2773                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
2774                         scanner.chew()
2775                         if ja() == True:
2776                             igotit = True
2777             # Only grant long life if alive (original didn't!)
2778             skip(1)
2779             prout(_("LIVE LONG AND PROSPER."))
2780         score()
2781         if igotit:
2782             plaque()        
2783         return
2784     elif ifin == FDEPLETE: # Federation Resources Depleted
2785         prout(_("Your time has run out and the Federation has been"))
2786         prout(_("conquered.  Your starship is now Klingon property,"))
2787         prout(_("and you are put on trial as a war criminal.  On the"))
2788         proutn(_("basis of your record, you are "))
2789         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
2790             prout(_("acquitted."))
2791             skip(1)
2792             prout(_("LIVE LONG AND PROSPER."))
2793         else:
2794             prout(_("found guilty and"))
2795             prout(_("sentenced to death by slow torture."))
2796             game.alive = False
2797         score()
2798         return
2799     elif ifin == FLIFESUP:
2800         prout(_("Your life support reserves have run out, and"))
2801         prout(_("you die of thirst, starvation, and asphyxiation."))
2802         prout(_("Your starship is a derelict in space."))
2803     elif ifin == FNRG:
2804         prout(_("Your energy supply is exhausted."))
2805         skip(1)
2806         prout(_("Your starship is a derelict in space."))
2807     elif ifin == FBATTLE:
2808         prout(_("The %s has been destroyed in battle.") % crmshp())
2809         skip(1)
2810         prout(_("Dulce et decorum est pro patria mori."))
2811     elif ifin == FNEG3:
2812         prout(_("You have made three attempts to cross the negative energy"))
2813         prout(_("barrier which surrounds the galaxy."))
2814         skip(1)
2815         prout(_("Your navigation is abominable."))
2816         score()
2817     elif ifin == FNOVA:
2818         prout(_("Your starship has been destroyed by a nova."))
2819         prout(_("That was a great shot."))
2820         skip(1)
2821     elif ifin == FSNOVAED:
2822         prout(_("The %s has been fried by a supernova.") % crmshp())
2823         prout(_("...Not even cinders remain..."))
2824     elif ifin == FABANDN:
2825         prout(_("You have been captured by the Klingons. If you still"))
2826         prout(_("had a starbase to be returned to, you would have been"))
2827         prout(_("repatriated and given another chance. Since you have"))
2828         prout(_("no starbases, you will be mercilessly tortured to death."))
2829     elif ifin == FDILITHIUM:
2830         prout(_("Your starship is now an expanding cloud of subatomic particles"))
2831     elif ifin == FMATERIALIZE:
2832         prout(_("Starbase was unable to re-materialize your starship."))
2833         prout(_("Sic transit gloria mundi"))
2834     elif ifin == FPHASER:
2835         prout(_("The %s has been cremated by its own phasers.") % crmshp())
2836     elif ifin == FLOST:
2837         prout(_("You and your landing party have been"))
2838         prout(_("converted to energy, disipating through space."))
2839     elif ifin == FMINING:
2840         prout(_("You are left with your landing party on"))
2841         prout(_("a wild jungle planet inhabited by primitive cannibals."))
2842         skip(1)
2843         prout(_("They are very fond of \"Captain Kirk\" soup."))
2844         skip(1)
2845         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2846     elif ifin == FDPLANET:
2847         prout(_("You and your mining party perish."))
2848         skip(1)
2849         prout(_("That was a great shot."))
2850         skip(1)
2851     elif ifin == FSSC:
2852         prout(_("The Galileo is instantly annihilated by the supernova."))
2853         prout(_("You and your mining party are atomized."))
2854         skip(1)
2855         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2856         prout(_("joins the Romulans, wreaking terror on the Federation."))
2857     elif ifin == FPNOVA:
2858         prout(_("You and your mining party are atomized."))
2859         skip(1)
2860         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2861         prout(_("joins the Romulans, wreaking terror on the Federation."))
2862     elif ifin == FSTRACTOR:
2863         prout(_("The shuttle craft Galileo is also caught,"))
2864         prout(_("and breaks up under the strain."))
2865         skip(1)
2866         prout(_("Your debris is scattered for millions of miles."))
2867         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2868     elif ifin == FDRAY:
2869         prout(_("The mutants attack and kill Spock."))
2870         prout(_("Your ship is captured by Klingons, and"))
2871         prout(_("your crew is put on display in a Klingon zoo."))
2872     elif ifin == FTRIBBLE:
2873         prout(_("Tribbles consume all remaining water,"))
2874         prout(_("food, and oxygen on your ship."))
2875         skip(1)
2876         prout(_("You die of thirst, starvation, and asphyxiation."))
2877         prout(_("Your starship is a derelict in space."))
2878     elif ifin == FHOLE:
2879         prout(_("Your ship is drawn to the center of the black hole."))
2880         prout(_("You are crushed into extremely dense matter."))
2881     elif ifin == FCREW:
2882         prout(_("Your last crew member has died."))
2883     if game.ship == 'F':
2884         game.ship = None
2885     elif game.ship == 'E':
2886         game.ship = 'F'
2887     game.alive = False
2888     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
2889         goodies = game.state.remres/game.inresor
2890         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
2891         if goodies/baddies >= randreal(1.0, 1.5):
2892             prout(_("As a result of your actions, a treaty with the Klingon"))
2893             prout(_("Empire has been signed. The terms of the treaty are"))
2894             if goodies/baddies >= randreal(3.0):
2895                 prout(_("favorable to the Federation."))
2896                 skip(1)
2897                 prout(_("Congratulations!"))
2898             else:
2899                 prout(_("highly unfavorable to the Federation."))
2900         else:
2901             prout(_("The Federation will be destroyed."))
2902     else:
2903         prout(_("Since you took the last Klingon with you, you are a"))
2904         prout(_("martyr and a hero. Someday maybe they'll erect a"))
2905         prout(_("statue in your memory. Rest in peace, and try not"))
2906         prout(_("to think about pigeons."))
2907         game.gamewon = True
2908     score()
2909
2910 def score():
2911     "Compute player's score."
2912     timused = game.state.date - game.indate
2913     iskill = game.skill
2914     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
2915         timused = 5.0
2916     perdate = killrate()
2917     ithperd = 500*perdate + 0.5
2918     iwon = 0
2919     if game.gamewon:
2920         iwon = 100*game.skill
2921     if game.ship == 'E': 
2922         klship = 0
2923     elif game.ship == 'F': 
2924         klship = 1
2925     else:
2926         klship = 2
2927     iscore = 10*(game.inkling - game.state.remkl) \
2928              + 50*(game.incom - len(game.state.kcmdr)) \
2929              + ithperd + iwon \
2930              + 20*(game.inrom - game.state.nromrem) \
2931              + 200*(game.inscom - game.state.nscrem) \
2932              - game.state.nromrem \
2933              - badpoints()
2934     if not game.alive:
2935         iscore -= 200
2936     skip(2)
2937     prout(_("Your score --"))
2938     if game.inrom - game.state.nromrem:
2939         prout(_("%6d Romulans destroyed                 %5d") %
2940               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
2941     if game.state.nromrem and game.gamewon:
2942         prout(_("%6d Romulans captured                  %5d") %
2943               (game.state.nromrem, game.state.nromrem))
2944     if game.inkling - game.state.remkl:
2945         prout(_("%6d ordinary Klingons destroyed        %5d") %
2946               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
2947     if game.incom - len(game.state.kcmdr):
2948         prout(_("%6d Klingon commanders destroyed       %5d") %
2949               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
2950     if game.inscom - game.state.nscrem:
2951         prout(_("%6d Super-Commander destroyed          %5d") %
2952               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
2953     if ithperd:
2954         prout(_("%6.2f Klingons per stardate              %5d") %
2955               (perdate, ithperd))
2956     if game.state.starkl:
2957         prout(_("%6d stars destroyed by your action     %5d") %
2958               (game.state.starkl, -5*game.state.starkl))
2959     if game.state.nplankl:
2960         prout(_("%6d planets destroyed by your action   %5d") %
2961               (game.state.nplankl, -10*game.state.nplankl))
2962     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
2963         prout(_("%6d inhabited planets destroyed by your action   %5d") %
2964               (game.state.nworldkl, -300*game.state.nworldkl))
2965     if game.state.basekl:
2966         prout(_("%6d bases destroyed by your action     %5d") %
2967               (game.state.basekl, -100*game.state.basekl))
2968     if game.nhelp:
2969         prout(_("%6d calls for help from starbase       %5d") %
2970               (game.nhelp, -45*game.nhelp))
2971     if game.casual:
2972         prout(_("%6d casualties incurred                %5d") %
2973               (game.casual, -game.casual))
2974     if game.abandoned:
2975         prout(_("%6d crew abandoned in space            %5d") %
2976               (game.abandoned, -3*game.abandoned))
2977     if klship:
2978         prout(_("%6d ship(s) lost or destroyed          %5d") %
2979               (klship, -100*klship))
2980     if not game.alive:
2981         prout(_("Penalty for getting yourself killed        -200"))
2982     if game.gamewon:
2983         proutn(_("Bonus for winning "))
2984         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
2985         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
2986         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
2987         elif game.skill ==  SKILL_EXPERT:       proutn(_("Expert game  "))
2988         elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
2989         prout("           %5d" % iwon)
2990     skip(1)
2991     prout(_("TOTAL SCORE                               %5d") % iscore)
2992
2993 def plaque():
2994     "Emit winner's commemmorative plaque." 
2995     skip(2)
2996     while True:
2997         proutn(_("File or device name for your plaque: "))
2998         winner = cgetline()
2999         try:
3000             fp = open(winner, "w")
3001             break
3002         except IOError:
3003             prout(_("Invalid name."))
3004
3005     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3006     winner = cgetline()
3007     # The 38 below must be 64 for 132-column paper 
3008     nskip = 38 - len(winner)/2
3009     fp.write("\n\n\n\n")
3010     # --------DRAW ENTERPRISE PICTURE. 
3011     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3012     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3013     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3014     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3015     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3016     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3017     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3018     fp.write("                                      EEE           E          E            E  E\n")
3019     fp.write("                                                       E         E          E  E\n")
3020     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3021     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3022     fp.write("                                                    :E    :                 EEEE       E\n")
3023     fp.write("                                                   .-E   -:-----                       E\n")
3024     fp.write("                                                    :E    :                            E\n")
3025     fp.write("                                                      EE  :                    EEEEEEEE\n")
3026     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3027     fp.write("\n\n\n")
3028     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3029     fp.write("\n\n\n\n")
3030     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3031     fp.write("\n")
3032     fp.write(_("                                                Starfleet Command bestows to you\n"))
3033     fp.write("\n")
3034     fp.write("%*s%s\n\n" % (nskip, "", winner))
3035     fp.write(_("                                                           the rank of\n\n"))
3036     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3037     fp.write("                                                          ")
3038     if game.skill ==  SKILL_EXPERT:
3039         fp.write(_(" Expert level\n\n"))
3040     elif game.skill == SKILL_EMERITUS:
3041         fp.write(_("Emeritus level\n\n"))
3042     else:
3043         fp.write(_(" Cheat level\n\n"))
3044     timestring = time.ctime()
3045     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3046                     (timestring+4, timestring+20, timestring+11))
3047     fp.write(_("                                                        Your score:  %d\n\n") % iscore)
3048     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % perdate)
3049     fp.close()
3050
3051 # Code from io.c begins here
3052
3053 rows = linecount = 0    # for paging 
3054 stdscr = None
3055 replayfp = None
3056 fullscreen_window = None
3057 srscan_window     = None
3058 report_window     = None
3059 status_window     = None
3060 lrscan_window     = None
3061 message_window    = None
3062 prompt_window     = None
3063 curwnd = None
3064
3065 def iostart():
3066     global stdscr, rows
3067     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3068     gettext.textdomain("sst")
3069     if not (game.options & OPTION_CURSES):
3070         ln_env = os.getenv("LINES")
3071         if ln_env:
3072             rows = ln_env
3073         else:
3074             rows = 25
3075     else:
3076         stdscr = curses.initscr()
3077         stdscr.keypad(True)
3078         curses.nonl()
3079         curses.cbreak()
3080         if game.options & OPTION_COLOR:
3081             curses.start_color();
3082             curses.use_default_colors()
3083             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1);
3084             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1);
3085             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1);
3086             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1);
3087             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1);
3088             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1);
3089             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1);
3090             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1);
3091         global fullscreen_window, srscan_window, report_window, status_window
3092         global lrscan_window, message_window, prompt_window
3093         (rows, columns)   = stdscr.getmaxyx()
3094         fullscreen_window = stdscr
3095         srscan_window     = curses.newwin(12, 25, 0,       0)
3096         report_window     = curses.newwin(11, 0,  1,       25)
3097         status_window     = curses.newwin(10, 0,  1,       39)
3098         lrscan_window     = curses.newwin(5,  0,  0,       64) 
3099         message_window    = curses.newwin(0,  0,  12,      0)
3100         prompt_window     = curses.newwin(1,  0,  rows-2,  0) 
3101         message_window.scrollok(True)
3102         setwnd(fullscreen_window)
3103
3104 def ioend():
3105     "Wrap up I/O."
3106     if game.options & OPTION_CURSES:
3107         stdscr.keypad(False)
3108         curses.echo()
3109         curses.nocbreak()
3110         curses.endwin()
3111
3112 def waitfor():
3113     "Wait for user action -- OK to do nothing if on a TTY"
3114     if game.options & OPTION_CURSES:
3115         stdscr.getch()
3116
3117 def announce():
3118     skip(1)
3119     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3120     skip(1)
3121
3122 def pause_game():
3123     if game.skill > SKILL_FAIR:
3124         prompt = _("[CONTINUE?]")
3125     else:
3126         prompt = _("[PRESS ENTER TO CONTINUE]")
3127
3128     if game.options & OPTION_CURSES:
3129         drawmaps(0)
3130         setwnd(prompt_window)
3131         prompt_window.clear()
3132         prompt_window.addstr(prompt)
3133         prompt_window.getstr()
3134         prompt_window.clear()
3135         prompt_window.refresh()
3136         setwnd(message_window)
3137     else:
3138         global linecount
3139         sys.stdout.write('\n')
3140         proutn(prompt)
3141         raw_input()
3142         for j in range(rows):
3143             sys.stdout.write('\n')
3144         linecount = 0
3145
3146 def skip(i):
3147     "Skip i lines.  Pause game if this would cause a scrolling event."
3148     for dummy in range(i):
3149         if game.options & OPTION_CURSES:
3150             (y, x) = curwnd.getyx()
3151             (my, mx) = curwnd.getmaxyx()
3152             if curwnd == message_window and y >= my - 3:
3153                 pause_game()
3154                 clrscr()
3155             else:
3156                 try:
3157                     curwnd.move(y+1, 0)
3158                 except curses.error:
3159                     pass
3160         else:
3161             global linecount
3162             linecount += 1
3163             if rows and linecount >= rows:
3164                 pause_game()
3165             else:
3166                 sys.stdout.write('\n')
3167
3168 def proutn(line):
3169     "Utter a line with no following line feed."
3170     if game.options & OPTION_CURSES:
3171         curwnd.addstr(line)
3172         curwnd.refresh()
3173     else:
3174         sys.stdout.write(line)
3175         sys.stdout.flush()
3176
3177 def prout(line):
3178     proutn(line)
3179     skip(1)
3180
3181 def prouts(line):
3182     "Emit slowly!" 
3183     for c in line:
3184         if not replayfp or replayfp.closed:     # Don't slow down replays
3185             time.sleep(0.03)
3186         proutn(c)
3187         if game.options & OPTION_CURSES:
3188             curwnd.refresh()
3189         else:
3190             sys.stdout.flush()
3191     if not replayfp or replayfp.closed:
3192         time.sleep(0.03)
3193
3194 def cgetline():
3195     "Get a line of input."
3196     if game.options & OPTION_CURSES:
3197         line = curwnd.getstr() + "\n"
3198         curwnd.refresh()
3199     else:
3200         if replayfp and not replayfp.closed:
3201             while True:
3202                 line = replayfp.readline()
3203                 proutn(line)
3204                 if line == '':
3205                     prout("*** Replay finished")
3206                     replayfp.close()
3207                     break
3208                 elif line[0] != "#":
3209                     break
3210         else:
3211             line = raw_input() + "\n"
3212     if logfp:
3213         logfp.write(line)
3214     return line
3215
3216 def setwnd(wnd):
3217     "Change windows -- OK for this to be a no-op in tty mode."
3218     global curwnd
3219     if game.options & OPTION_CURSES:
3220         curwnd = wnd
3221         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
3222
3223 def clreol():
3224     "Clear to end of line -- can be a no-op in tty mode" 
3225     if game.options & OPTION_CURSES:
3226         curwnd.clrtoeol()
3227         curwnd.refresh()
3228
3229 def clrscr():
3230     "Clear screen -- can be a no-op in tty mode."
3231     global linecount
3232     if game.options & OPTION_CURSES:
3233        curwnd.clear()
3234        curwnd.move(0, 0)
3235        curwnd.refresh()
3236     linecount = 0
3237
3238 def textcolor(color=DEFAULT):
3239     if game.options & OPTION_COLOR:
3240         if color == DEFAULT: 
3241             curwnd.attrset(0);
3242         elif color ==  BLACK: 
3243             curwnd.attron(curses.color_pair(curses.COLOR_BLACK));
3244         elif color ==  BLUE: 
3245             curwnd.attron(curses.color_pair(curses.COLOR_BLUE));
3246         elif color ==  GREEN: 
3247             curwnd.attron(curses.color_pair(curses.COLOR_GREEN));
3248         elif color ==  CYAN: 
3249             curwnd.attron(curses.color_pair(curses.COLOR_CYAN));
3250         elif color ==  RED: 
3251             curwnd.attron(curses.color_pair(curses.COLOR_RED));
3252         elif color ==  MAGENTA: 
3253             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA));
3254         elif color ==  BROWN: 
3255             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW));
3256         elif color ==  LIGHTGRAY: 
3257             curwnd.attron(curses.color_pair(curses.COLOR_WHITE));
3258         elif color ==  DARKGRAY: 
3259             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD);
3260         elif color ==  LIGHTBLUE: 
3261             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD);
3262         elif color ==  LIGHTGREEN: 
3263             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD);
3264         elif color ==  LIGHTCYAN: 
3265             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD);
3266         elif color ==  LIGHTRED: 
3267             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD);
3268         elif color ==  LIGHTMAGENTA: 
3269             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD);
3270         elif color ==  YELLOW: 
3271             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD);
3272         elif color ==  WHITE:
3273             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD);
3274
3275 def highvideo():
3276     if game.options & OPTION_COLOR:
3277         curwnd.attron(curses.A_REVERSE)
3278
3279 #
3280 # Things past this point have policy implications.
3281
3282
3283 def drawmaps(mode):
3284     "Hook to be ca