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