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