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