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