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