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