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