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