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