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