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