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