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