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