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