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