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