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