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