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