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