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