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