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