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