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