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