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