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