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