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