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