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