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