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