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