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