Repair the distribution builder.
[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         setwnd(message_window)
1103         if not damaged(DSRSENS) or game.condition == "docked":
1104             skip(1)     # start new line after text track 
1105         if iquad in ('E', 'F'): # Hit our ship 
1106             skip(1)
1107             prout(_("Torpedo hits %s.") % crmshp())
1108             hit = 700.0 + randreal(100) - \
1109                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1110             newcnd() # we're blown out of dock 
1111             if game.landed or game.condition == "docked":
1112                 return hit # Cheat if on a planet 
1113             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1114             # is 143 degrees, which is almost exactly 4.8 clockface units
1115             displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
1116             displacement.next()
1117             bumpto = displacement.sector()
1118             if not bumpto.valid_sector():
1119                 return hit
1120             if game.quad[bumpto.i][bumpto.j] == ' ':
1121                 finish(FHOLE)
1122                 return hit
1123             if game.quad[bumpto.i][bumpto.j] != '.':
1124                 # can't move into object 
1125                 return hit
1126             game.sector = bumpto
1127             proutn(crmshp())
1128             game.quad[w.i][w.j] = '.'
1129             game.quad[bumpto.i][bumpto.j] = iquad
1130             prout(_(" displaced by blast to Sector %s ") % bumpto)
1131             for enemy in game.enemies:
1132                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1133             sortenemies()
1134             return None
1135         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy 
1136             # find the enemy 
1137             if iquad in ('C', 'S') and withprob(0.05):
1138                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1139                 prout(_("   torpedo neutralized."))
1140                 return None
1141             for enemy in game.enemies:
1142                 if w == enemy.location:
1143                     break
1144             kp = math.fabs(enemy.power)
1145             h1 = 700.0 + randrange(100) - \
1146                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1147             h1 = math.fabs(h1)
1148             if kp < h1:
1149                 h1 = kp
1150             if enemy.power < 0:
1151                 enemy.power -= -h1
1152             else:
1153                 enemy.power -= h1
1154             if enemy.power == 0:
1155                 deadkl(w, iquad, w)
1156                 return None
1157             proutn(crmena(True, iquad, "sector", w))
1158             displacement = course(track.bearing+randreal(-2.4, 2.4), distance=2**0.5)
1159             displacement.next()
1160             bumpto = displacement.sector()
1161             if not bumpto.valid_sector():
1162                 prout(_(" damaged but not destroyed."))
1163                 return
1164             if game.quad[bumpto.i][bumpto.j] == ' ':
1165                 prout(_(" buffeted into black hole."))
1166                 deadkl(w, iquad, bumpto)
1167             if game.quad[bumpto.i][bumpto.j] != '.':
1168                 prout(_(" damaged but not destroyed."))
1169             else:
1170                 prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1171                 enemy.location = bumpto
1172                 game.quad[w.i][w.j] = '.'
1173                 game.quad[bumpto.i][bumpto.j] = iquad
1174                 for enemy in game.enemies:
1175                     enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1176                 sortenemies()
1177             return None
1178         elif iquad == 'B': # Hit a base 
1179             skip(1)
1180             prout(_("***STARBASE DESTROYED.."))
1181             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
1182             game.quad[w.i][w.j] = '.'
1183             game.base.invalidate()
1184             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
1185             game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
1186             game.state.basekl += 1
1187             newcnd()
1188             return None
1189         elif iquad == 'P': # Hit a planet 
1190             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1191             game.state.nplankl += 1
1192             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1193             game.iplnet.pclass = "destroyed"
1194             game.iplnet = None
1195             game.plnet.invalidate()
1196             game.quad[w.i][w.j] = '.'
1197             if game.landed:
1198                 # captain perishes on planet 
1199                 finish(FDPLANET)
1200             return None
1201         elif iquad == '@': # Hit an inhabited world -- very bad! 
1202             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1203             game.state.nworldkl += 1
1204             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1205             game.iplnet.pclass = "destroyed"
1206             game.iplnet = None
1207             game.plnet.invalidate()
1208             game.quad[w.i][w.j] = '.'
1209             if game.landed:
1210                 # captain perishes on planet 
1211                 finish(FDPLANET)
1212             prout(_("The torpedo destroyed an inhabited planet."))
1213             return None
1214         elif iquad == '*': # Hit a star 
1215             if withprob(0.9):
1216                 nova(w)
1217             else:
1218                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1219             return None
1220         elif iquad == '?': # Hit a thingy 
1221             if not (game.options & OPTION_THINGY) or withprob(0.3):
1222                 skip(1)
1223                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1224                 skip(1)
1225                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1226                 skip(1)
1227                 proutn(_("Mr. Spock-"))
1228                 prouts(_("  \"Fascinating!\""))
1229                 skip(1)
1230                 deadkl(w, iquad, w)
1231             else:
1232                 # Stas Sergeev added the possibility that
1233                 # you can shove the Thingy and piss it off.
1234                 # It then becomes an enemy and may fire at you.
1235                 thing.angry()
1236             return None
1237         elif iquad == ' ': # Black hole 
1238             skip(1)
1239             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1240             return None
1241         elif iquad == '#': # hit the web 
1242             skip(1)
1243             prout(_("***Torpedo absorbed by Tholian web."))
1244             return None
1245         elif iquad == 'T':  # Hit a Tholian 
1246             h1 = 700.0 + randrange(100) - \
1247                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1248             h1 = math.fabs(h1)
1249             if h1 >= 600:
1250                 game.quad[w.i][w.j] = '.'
1251                 deadkl(w, iquad, w)
1252                 game.tholian = None
1253                 return None
1254             skip(1)
1255             proutn(crmena(True, 'T', "sector", w))
1256             if withprob(0.05):
1257                 prout(_(" survives photon blast."))
1258                 return None
1259             prout(_(" disappears."))
1260             game.tholian.move(None)
1261             game.quad[w.i][w.j] = '#'
1262             dropin(' ')
1263             return None
1264         else: # Problem!
1265             skip(1)
1266             proutn("Don't know how to handle torpedo collision with ")
1267             proutn(crmena(True, iquad, "sector", w))
1268             skip(1)
1269             return None
1270         break
1271     skip(1)
1272     prout(_("Torpedo missed."))
1273     return None
1274
1275 def fry(hit):
1276     "Critical-hit resolution." 
1277     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1278         return
1279     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1280     proutn(_("***CRITICAL HIT--"))
1281     # Select devices and cause damage
1282     cdam = []
1283     while ncrit > 0:
1284         ncrit -= 1
1285         while True:
1286             j = randdevice()
1287             # Cheat to prevent shuttle damage unless on ship 
1288             if not (game.damage[j]<0.0 or (j == DSHUTTL and game.iscraft != "onship")):
1289                 break
1290         cdam.append(j)
1291         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1292         game.damage[j] += extradm
1293     skipcount = 0
1294     for (i, j) in enumerate(cdam):
1295         proutn(device[j])
1296         if skipcount % 3 == 2 and i < len(cdam)-1:
1297             skip(1)
1298         skipcount += 1
1299         if i < len(cdam)-1:
1300             proutn(_(" and "))
1301     prout(_(" damaged."))
1302     if damaged(DSHIELD) and game.shldup:
1303         prout(_("***Shields knocked down."))
1304         game.shldup = False
1305
1306 def attack(torps_ok):
1307     # bad guy attacks us 
1308     # torps_ok == False forces use of phasers in an attack 
1309     # game could be over at this point, check
1310     if game.alldone:
1311         return
1312     attempt = False
1313     ihurt = False
1314     hitmax = 0.0
1315     hittot = 0.0
1316     chgfac = 1.0
1317     where = "neither"
1318     if game.idebug:
1319         prout("=== ATTACK!")
1320     # Tholian gets to move before attacking 
1321     if game.tholian:
1322         movetholian()
1323     # if you have just entered the RNZ, you'll get a warning 
1324     if game.neutz: # The one chance not to be attacked 
1325         game.neutz = False
1326         return
1327     # commanders get a chance to tac-move towards you 
1328     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:
1329         moveklings()
1330     # if no enemies remain after movement, we're done 
1331     if len(game.enemies) == 0 or (len(game.enemies) == 1 and thing == game.quadrant and not thing.angered):
1332         return
1333     # set up partial hits if attack happens during shield status change 
1334     pfac = 1.0/game.inshld
1335     if game.shldchg:
1336         chgfac = 0.25 + randreal(0.5)
1337     skip(1)
1338     # message verbosity control 
1339     if game.skill <= SKILL_FAIR:
1340         where = "sector"
1341     for enemy in game.enemies:
1342         if enemy.power < 0:
1343             continue    # too weak to attack 
1344         # compute hit strength and diminish shield power 
1345         r = randreal()
1346         # Increase chance of photon torpedos if docked or enemy energy is low 
1347         if game.condition == "docked":
1348             r *= 0.25
1349         if enemy.power < 500:
1350             r *= 0.25 
1351         if enemy.type == 'T' or (enemy.type == '?' and not thing.angered):
1352             continue
1353         # different enemies have different probabilities of throwing a torp 
1354         usephasers = not torps_ok or \
1355             (enemy.type == 'K' and r > 0.0005) or \
1356             (enemy.type == 'C' and r > 0.015) or \
1357             (enemy.type == 'R' and r > 0.3) or \
1358             (enemy.type == 'S' and r > 0.07) or \
1359             (enemy.type == '?' and r > 0.05)
1360         if usephasers:      # Enemy uses phasers 
1361             if game.condition == "docked":
1362                 continue # Don't waste the effort! 
1363             attempt = True # Attempt to attack 
1364             dustfac = randreal(0.8, 0.85)
1365             hit = enemy.power*math.pow(dustfac, enemy.kavgd)
1366             enemy.power *= 0.75
1367         else: # Enemy uses photon torpedo 
1368             # We should be able to make the bearing() method work here
1369             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1370             hit = 0
1371             proutn(_("***TORPEDO INCOMING"))
1372             if not damaged(DSRSENS):
1373                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1374             attempt = True
1375             prout("  ")
1376             dispersion = (randreal()+randreal())*0.5 - 0.5
1377             dispersion += 0.002*enemy.power*dispersion
1378             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1379             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
1380                 finish(FWON) # Klingons did themselves in! 
1381             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1382                 return # Supernova or finished 
1383             if hit == None:
1384                 continue
1385         # incoming phaser or torpedo, shields may dissipate it 
1386         if game.shldup or game.shldchg or game.condition == "docked":
1387             # shields will take hits 
1388             propor = pfac * game.shield
1389             if game.condition == "docked":
1390                 propor *= 2.1
1391             if propor < 0.1:
1392                 propor = 0.1
1393             hitsh = propor*chgfac*hit+1.0
1394             absorb = 0.8*hitsh
1395             if absorb > game.shield:
1396                 absorb = game.shield
1397             game.shield -= absorb
1398             hit -= hitsh
1399             # taking a hit blasts us out of a starbase dock 
1400             if game.condition == "docked":
1401                 dock(False)
1402             # but the shields may take care of it 
1403             if propor > 0.1 and hit < 0.005*game.energy:
1404                 continue
1405         # hit from this opponent got through shields, so take damage 
1406         ihurt = True
1407         proutn(_("%d unit hit") % int(hit))
1408         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1409             proutn(_(" on the ") + crmshp())
1410         if not damaged(DSRSENS) and usephasers:
1411             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1412         skip(1)
1413         # Decide if hit is critical 
1414         if hit > hitmax:
1415             hitmax = hit
1416         hittot += hit
1417         fry(hit)
1418         game.energy -= hit
1419     if game.energy <= 0:
1420         # Returning home upon your shield, not with it... 
1421         finish(FBATTLE)
1422         return
1423     if not attempt and game.condition == "docked":
1424         prout(_("***Enemies decide against attacking your ship."))
1425     percent = 100.0*pfac*game.shield+0.5
1426     if not ihurt:
1427         # Shields fully protect ship 
1428         proutn(_("Enemy attack reduces shield strength to "))
1429     else:
1430         # Emit message if starship suffered hit(s) 
1431         skip(1)
1432         proutn(_("Energy left %2d    shields ") % int(game.energy))
1433         if game.shldup:
1434             proutn(_("up "))
1435         elif not damaged(DSHIELD):
1436             proutn(_("down "))
1437         else:
1438             proutn(_("damaged, "))
1439     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1440     # Check if anyone was hurt 
1441     if hitmax >= 200 or hittot >= 500:
1442         icas = randrange(int(hittot * 0.015))
1443         if icas >= 2:
1444             skip(1)
1445             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1446             prout(_("   in that last attack.\""))
1447             game.casual += icas
1448             game.state.crew -= icas
1449     # After attack, reset average distance to enemies 
1450     for enemy in game.enemies:
1451         enemy.kavgd = enemy.kdist
1452     sortenemies()
1453     return
1454                 
1455 def deadkl(w, etype, mv):
1456     "Kill a Klingon, Tholian, Romulan, or Thingy." 
1457     # Added mv to allow enemy to "move" before dying 
1458     proutn(crmena(True, etype, "sector", mv))
1459     # Decide what kind of enemy it is and update appropriately 
1460     if etype == 'R':
1461         # Chalk up a Romulan 
1462         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1463         game.irhere -= 1
1464         game.state.nromrem -= 1
1465     elif etype == 'T':
1466         # Killed a Tholian 
1467         game.tholian = None
1468     elif etype == '?':
1469         # Killed a Thingy
1470         global thing
1471         thing = None
1472     else:
1473         # Killed some type of Klingon 
1474         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1475         game.klhere -= 1
1476         if type == 'C':
1477             game.state.kcmdr.remove(game.quadrant)
1478             unschedule(FTBEAM)
1479             if game.state.kcmdr:
1480                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1481             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1482                 unschedule(FCDBAS)    
1483         elif type ==  'K':
1484             game.state.remkl -= 1
1485         elif type ==  'S':
1486             game.state.nscrem -= 1
1487             game.state.kscmdr.invalidate()
1488             game.isatb = 0
1489             game.iscate = False
1490             unschedule(FSCMOVE)
1491             unschedule(FSCDBAS)
1492     # For each kind of enemy, finish message to player 
1493     prout(_(" destroyed."))
1494     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
1495         return
1496     game.recompute()
1497     # Remove enemy ship from arrays describing local conditions
1498     for e in game.enemies:
1499         if e.location == w:
1500             e.move(None)
1501             break
1502     return
1503
1504 def targetcheck(w):
1505     "Return None if target is invalid, otherwise return a course angle."
1506     if not w.valid_sector():
1507         huh()
1508         return None
1509     delta = Coord()
1510     # C code this was translated from is wacky -- why the sign reversal?
1511     delta.j = (w.j - game.sector.j)
1512     delta.i = (game.sector.i - w.i)
1513     if delta == Coord(0, 0):
1514         skip(1)
1515         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1516         prout(_("  I recommend an immediate review of"))
1517         prout(_("  the Captain's psychological profile.\""))
1518         scanner.chew()
1519         return None
1520     return delta.bearing()
1521
1522 def torps():
1523     "Launch photon torpedo salvo."
1524     tcourse = []
1525     game.ididit = False
1526     if damaged(DPHOTON):
1527         prout(_("Photon tubes damaged."))
1528         scanner.chew()
1529         return
1530     if game.torps == 0:
1531         prout(_("No torpedoes left."))
1532         scanner.chew()
1533         return
1534     # First, get torpedo count
1535     while True:
1536         scanner.next()
1537         if scanner.token == "IHALPHA":
1538             huh()
1539             return
1540         elif scanner.token == "IHEOL" or not scanner.waiting():
1541             prout(_("%d torpedoes left.") % game.torps)
1542             scanner.chew()
1543             proutn(_("Number of torpedoes to fire- "))
1544             continue    # Go back around to get a number
1545         else: # key == "IHREAL"
1546             n = scanner.int()
1547             if n <= 0: # abort command 
1548                 scanner.chew()
1549                 return
1550             if n > MAXBURST:
1551                 scanner.chew()
1552                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1553                 return
1554             if n > game.torps:
1555                 scanner.chew()  # User requested more torps than available
1556                 continue        # Go back around
1557             break       # All is good, go to next stage
1558     # Next, get targets
1559     target = []
1560     for i in range(n):
1561         key = scanner.next()
1562         if i == 0 and key == "IHEOL":
1563             break       # no coordinate waiting, we will try prompting 
1564         if i == 1 and key == "IHEOL":
1565             # direct all torpedoes at one target 
1566             while i < n:
1567                 target.append(target[0])
1568                 tcourse.append(tcourse[0])
1569                 i += 1
1570             break
1571         scanner.push(scanner.token)
1572         target.append(scanner.getcoord())
1573         if target[-1] == None:
1574             return
1575         tcourse.append(targetcheck(target[-1]))
1576         if tcourse[-1] == None:
1577             return
1578     scanner.chew()
1579     if len(target) == 0:
1580         # prompt for each one 
1581         for i in range(n):
1582             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1583             scanner.chew()
1584             target.append(scanner.getcoord())
1585             if target[-1] == None:
1586                 return
1587             tcourse.append(targetcheck(target[-1]))
1588             if tcourse[-1] == None:
1589                 return
1590     game.ididit = True
1591     # Loop for moving <n> torpedoes 
1592     for i in range(n):
1593         if game.condition != "docked":
1594             game.torps -= 1
1595         dispersion = (randreal()+randreal())*0.5 -0.5
1596         if math.fabs(dispersion) >= 0.47:
1597             # misfire! 
1598             dispersion *= randreal(1.2, 2.2)
1599             if n > 0:
1600                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1601             else:
1602                 prouts(_("***TORPEDO MISFIRES."))
1603             skip(1)
1604             if i < n:
1605                 prout(_("  Remainder of burst aborted."))
1606             if withprob(0.2):
1607                 prout(_("***Photon tubes damaged by misfire."))
1608                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1609             break
1610         if game.shldup or game.condition == "docked":
1611             dispersion *= 1.0 + 0.0001*game.shield
1612         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1613         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1614             return
1615     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
1616         finish(FWON)
1617
1618 def overheat(rpow):
1619     "Check for phasers overheating."
1620     if rpow > 1500:
1621         checkburn = (rpow-1500.0)*0.00038
1622         if withprob(checkburn):
1623             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1624             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1625
1626 def checkshctrl(rpow):
1627     "Check shield control."
1628     skip(1)
1629     if withprob(0.998):
1630         prout(_("Shields lowered."))
1631         return False
1632     # Something bad has happened 
1633     prouts(_("***RED ALERT!  RED ALERT!"))
1634     skip(2)
1635     hit = rpow*game.shield/game.inshld
1636     game.energy -= rpow+hit*0.8
1637     game.shield -= hit*0.2
1638     if game.energy <= 0.0:
1639         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1640         skip(1)
1641         stars()
1642         finish(FPHASER)
1643         return True
1644     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1645     skip(2)
1646     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1647     icas = randrange(int(hit*0.012))
1648     skip(1)
1649     fry(0.8*hit)
1650     if icas:
1651         skip(1)
1652         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1653         prout(_("  %d casualties so far.\"") % icas)
1654         game.casual += icas
1655         game.state.crew -= icas
1656     skip(1)
1657     prout(_("Phaser energy dispersed by shields."))
1658     prout(_("Enemy unaffected."))
1659     overheat(rpow)
1660     return True
1661
1662 def hittem(hits):
1663     "Register a phaser hit on Klingons and Romulans."
1664     w = Coord()
1665     skip(1)
1666     for (kk, wham) in enumerate(hits):
1667         if wham == 0:
1668             continue
1669         dustfac = randreal(0.9, 1.0)
1670         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1671         kpini = game.enemies[kk].power
1672         kp = math.fabs(kpini)
1673         if PHASEFAC*hit < kp:
1674             kp = PHASEFAC*hit
1675         if game.enemies[kk].power < 0:
1676             game.enemies[kk].power -= -kp
1677         else:
1678             game.enemies[kk].power -= kp
1679         kpow = game.enemies[kk].power
1680         w = game.enemies[kk].location
1681         if hit > 0.005:
1682             if not damaged(DSRSENS):
1683                 boom(w)
1684             proutn(_("%d unit hit on ") % int(hit))
1685         else:
1686             proutn(_("Very small hit on "))
1687         ienm = game.quad[w.i][w.j]
1688         if ienm == '?':
1689             thing.angry()
1690         proutn(crmena(False, ienm, "sector", w))
1691         skip(1)
1692         if kpow == 0:
1693             deadkl(w, ienm, w)
1694             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1695                 finish(FWON)            
1696             if game.alldone:
1697                 return
1698             kk -= 1     # don't do the increment
1699             continue
1700         else: # decide whether or not to emasculate klingon 
1701             if kpow > 0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1702                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1703                 prout(_("   has just lost its firepower.\""))
1704                 game.enemies[kk].power = -kpow
1705         kk += 1
1706     return
1707
1708 def phasers():
1709     "Fire phasers at bad guys."
1710     hits = []
1711     kz = 0
1712     k = 1
1713     irec = 0 # Cheating inhibitor 
1714     ifast = False
1715     no = False
1716     itarg = True
1717     msgflag = True
1718     rpow = 0
1719     automode = "NOTSET"
1720     key = 0
1721     skip(1)
1722     # SR sensors and Computer are needed for automode 
1723     if damaged(DSRSENS) or damaged(DCOMPTR):
1724         itarg = False
1725     if game.condition == "docked":
1726         prout(_("Phasers can't be fired through base shields."))
1727         scanner.chew()
1728         return
1729     if damaged(DPHASER):
1730         prout(_("Phaser control damaged."))
1731         scanner.chew()
1732         return
1733     if game.shldup:
1734         if damaged(DSHCTRL):
1735             prout(_("High speed shield control damaged."))
1736             scanner.chew()
1737             return
1738         if game.energy <= 200.0:
1739             prout(_("Insufficient energy to activate high-speed shield control."))
1740             scanner.chew()
1741             return
1742         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1743         ifast = True
1744     # Original code so convoluted, I re-did it all
1745     # (That was Tom Almy talking about the C code, I think -- ESR)
1746     while automode == "NOTSET":
1747         key = scanner.next()
1748         if key == "IHALPHA":
1749             if scanner.sees("manual"):
1750                 if len(game.enemies)==0:
1751                     prout(_("There is no enemy present to select."))
1752                     scanner.chew()
1753                     key = "IHEOL"
1754                     automode = "AUTOMATIC"
1755                 else:
1756                     automode = "MANUAL"
1757                     key = scanner.next()
1758             elif scanner.sees("automatic"):
1759                 if (not itarg) and len(game.enemies) != 0:
1760                     automode = "FORCEMAN"
1761                 else:
1762                     if len(game.enemies)==0:
1763                         prout(_("Energy will be expended into space."))
1764                     automode = "AUTOMATIC"
1765                     key = scanner.next()
1766             elif scanner.sees("no"):
1767                 no = True
1768             else:
1769                 huh()
1770                 return
1771         elif key == "IHREAL":
1772             if len(game.enemies)==0:
1773                 prout(_("Energy will be expended into space."))
1774                 automode = "AUTOMATIC"
1775             elif not itarg:
1776                 automode = "FORCEMAN"
1777             else:
1778                 automode = "AUTOMATIC"
1779         else:
1780             # "IHEOL" 
1781             if len(game.enemies)==0:
1782                 prout(_("Energy will be expended into space."))
1783                 automode = "AUTOMATIC"
1784             elif not itarg:
1785                 automode = "FORCEMAN"
1786             else: 
1787                 proutn(_("Manual or automatic? "))
1788                 scanner.chew()
1789     avail = game.energy
1790     if ifast:
1791         avail -= 200.0
1792     if automode == "AUTOMATIC":
1793         if key == "IHALPHA" and scanner.sees("no"):
1794             no = True
1795             key = scanner.next()
1796         if key != "IHREAL" and len(game.enemies) != 0:
1797             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1798         irec = 0
1799         while True:
1800             scanner.chew()
1801             if not kz:
1802                 for i in range(len(game.enemies)):
1803                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1804             kz = 1
1805             proutn(_("%d units required. ") % irec)
1806             scanner.chew()
1807             proutn(_("Units to fire= "))
1808             key = scanner.next()
1809             if key != "IHREAL":
1810                 return
1811             rpow = scanner.real
1812             if rpow > avail:
1813                 proutn(_("Energy available= %.2f") % avail)
1814                 skip(1)
1815                 key = "IHEOL"
1816             if not rpow > avail:
1817                 break
1818         if rpow <= 0:
1819             # chicken out 
1820             scanner.chew()
1821             return
1822         key = scanner.next()
1823         if key == "IHALPHA" and scanner.sees("no"):
1824             no = True
1825         if ifast:
1826             game.energy -= 200 # Go and do it! 
1827             if checkshctrl(rpow):
1828                 return
1829         scanner.chew()
1830         game.energy -= rpow
1831         extra = rpow
1832         if len(game.enemies):
1833             extra = 0.0
1834             powrem = rpow
1835             for i in range(len(game.enemies)):
1836                 hits.append(0.0)
1837                 if powrem <= 0:
1838                     continue
1839                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
1840                 over = randreal(1.01, 1.06) * hits[i]
1841                 temp = powrem
1842                 powrem -= hits[i] + over
1843                 if powrem <= 0 and temp < hits[i]:
1844                     hits[i] = temp
1845                 if powrem <= 0:
1846                     over = 0.0
1847                 extra += over
1848             if powrem > 0.0:
1849                 extra += powrem
1850             hittem(hits)
1851             game.ididit = True
1852         if extra > 0 and not game.alldone:
1853             if game.tholian:
1854                 proutn(_("*** Tholian web absorbs "))
1855                 if len(game.enemies)>0:
1856                     proutn(_("excess "))
1857                 prout(_("phaser energy."))
1858             else:
1859                 prout(_("%d expended on empty space.") % int(extra))
1860     elif automode == "FORCEMAN":
1861         scanner.chew()
1862         key = "IHEOL"
1863         if damaged(DCOMPTR):
1864             prout(_("Battle computer damaged, manual fire only."))
1865         else:
1866             skip(1)
1867             prouts(_("---WORKING---"))
1868             skip(1)
1869             prout(_("Short-range-sensors-damaged"))
1870             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1871             prout(_("Manual-fire-must-be-used"))
1872             skip(1)
1873     elif automode == "MANUAL":
1874         rpow = 0.0
1875         for k in range(len(game.enemies)):
1876             aim = game.enemies[k].location
1877             ienm = game.quad[aim.i][aim.j]
1878             if msgflag:
1879                 proutn(_("Energy available= %.2f") % (avail-0.006))
1880                 skip(1)
1881                 msgflag = False
1882                 rpow = 0.0
1883             if damaged(DSRSENS) and \
1884                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
1885                 prout(cramen(ienm) + _(" can't be located without short range scan."))
1886                 scanner.chew()
1887                 key = "IHEOL"
1888                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko 
1889                 k += 1
1890                 continue
1891             if key == "IHEOL":
1892                 scanner.chew()
1893                 if itarg and k > kz:
1894                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *       randreal(1.01, 1.06) + 1.0
1895                 kz = k
1896                 proutn("(")
1897                 if not damaged(DCOMPTR):
1898                     proutn("%d" % irec)
1899                 else:
1900                     proutn("??")
1901                 proutn(")  ")
1902                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))                
1903                 key = scanner.next()
1904             if key == "IHALPHA" and scanner.sees("no"):
1905                 no = True
1906                 key = scanner.next()
1907                 continue
1908             if key == "IHALPHA":
1909                 huh()
1910                 return
1911             if key == "IHEOL":
1912                 if k == 1: # Let me say I'm baffled by this 
1913                     msgflag = True
1914                 continue
1915             if scanner.real < 0:
1916                 # abort out 
1917                 scanner.chew()
1918                 return
1919             hits[k] = scanner.real
1920             rpow += scanner.real
1921             # If total requested is too much, inform and start over 
1922             if rpow > avail:
1923                 prout(_("Available energy exceeded -- try again."))
1924                 scanner.chew()
1925                 return
1926             key = scanner.next() # scan for next value 
1927             k += 1
1928         if rpow == 0.0:
1929             # zero energy -- abort 
1930             scanner.chew()
1931             return
1932         if key == "IHALPHA" and scanner.sees("no"):
1933             no = True
1934         game.energy -= rpow
1935         scanner.chew()
1936         if ifast:
1937             game.energy -= 200.0
1938             if checkshctrl(rpow):
1939                 return
1940         hittem(hits)
1941         game.ididit = True
1942      # Say shield raised or malfunction, if necessary 
1943     if game.alldone:
1944         return
1945     if ifast:
1946         skip(1)
1947         if no == 0:
1948             if withprob(0.01):
1949                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
1950                 prouts(_("         CLICK   CLICK   POP  . . ."))
1951                 prout(_(" No response, sir!"))
1952                 game.shldup = False
1953             else:
1954                 prout(_("Shields raised."))
1955         else:
1956             game.shldup = False
1957     overheat(rpow)
1958
1959 # Code from events,c begins here.
1960
1961 # This isn't a real event queue a la BSD Trek yet -- you can only have one 
1962 # event of each type active at any given time.  Mostly these means we can 
1963 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
1964 # BSD Trek, from which we swiped the idea, can have up to 5.
1965
1966 def unschedule(evtype):
1967     "Remove an event from the schedule."
1968     game.future[evtype].date = FOREVER
1969     return game.future[evtype]
1970
1971 def is_scheduled(evtype):
1972     "Is an event of specified type scheduled."
1973     return game.future[evtype].date != FOREVER
1974
1975 def scheduled(evtype):
1976     "When will this event happen?"
1977     return game.future[evtype].date
1978
1979 def schedule(evtype, offset):
1980     "Schedule an event of specified type."
1981     game.future[evtype].date = game.state.date + offset
1982     return game.future[evtype]
1983
1984 def postpone(evtype, offset):
1985     "Postpone a scheduled event."
1986     game.future[evtype].date += offset
1987
1988 def cancelrest():
1989     "Rest period is interrupted by event."
1990     if game.resting:
1991         skip(1)
1992         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
1993         if ja():
1994             game.resting = False
1995             game.optime = 0.0
1996             return True
1997     return False
1998
1999 def events():
2000     "Run through the event queue looking for things to do."
2001     i = 0
2002     fintim = game.state.date + game.optime
2003     yank = 0
2004     ictbeam = False
2005     istract = False
2006     w = Coord()
2007     hold = Coord()
2008     ev = Event()
2009     ev2 = Event()
2010
2011     def tractorbeam(yank):
2012         "Tractor-beaming cases merge here." 
2013         announce()
2014         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5) 
2015         skip(1)
2016         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2017         # If Kirk & Co. screwing around on planet, handle 
2018         atover(True) # atover(true) is Grab 
2019         if game.alldone:
2020             return
2021         if game.icraft: # Caught in Galileo? 
2022             finish(FSTRACTOR)
2023             return
2024         # Check to see if shuttle is aboard 
2025         if game.iscraft == "offship":
2026             skip(1)
2027             if withprob(0.5):
2028                 prout(_("Galileo, left on the planet surface, is captured"))
2029                 prout(_("by aliens and made into a flying McDonald's."))
2030                 game.damage[DSHUTTL] = -10
2031                 game.iscraft = "removed"
2032             else:
2033                 prout(_("Galileo, left on the planet surface, is well hidden."))
2034         if evcode == FSPY:
2035             game.quadrant = game.state.kscmdr
2036         else:
2037             game.quadrant = game.state.kcmdr[i]
2038         game.sector = randplace(QUADSIZE)
2039         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2040                % (game.quadrant, game.sector))
2041         if game.resting:
2042             prout(_("(Remainder of rest/repair period cancelled.)"))
2043             game.resting = False
2044         if not game.shldup:
2045             if not damaged(DSHIELD) and game.shield > 0:
2046                 doshield(shraise=True) # raise shields 
2047                 game.shldchg = False
2048             else:
2049                 prout(_("(Shields not currently useable.)"))
2050         newqad()
2051         # Adjust finish time to time of tractor beaming 
2052         fintim = game.state.date+game.optime
2053         attack(torps_ok=False)
2054         if not game.state.kcmdr:
2055             unschedule(FTBEAM)
2056         else: 
2057             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2058
2059     def destroybase():
2060         "Code merges here for any commander destroying a starbase." 
2061         # Not perfect, but will have to do 
2062         # Handle case where base is in same quadrant as starship 
2063         if game.battle == game.quadrant:
2064             game.state.chart[game.battle.i][game.battle.j].starbase = False
2065             game.quad[game.base.i][game.base.j] = '.'
2066             game.base.invalidate()
2067             newcnd()
2068             skip(1)
2069             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2070         elif game.state.baseq and communicating():
2071             # Get word via subspace radio 
2072             announce()
2073             skip(1)
2074             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2075             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2076             if game.isatb == 2: 
2077                 prout(_("the Klingon Super-Commander"))
2078             else:
2079                 prout(_("a Klingon Commander"))
2080             game.state.chart[game.battle.i][game.battle.j].starbase = False
2081         # Remove Starbase from galaxy 
2082         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2083         game.state.baseq = filter(lambda x: x != game.battle, game.state.baseq)
2084         if game.isatb == 2:
2085             # reinstate a commander's base attack 
2086             game.battle = hold
2087             game.isatb = 0
2088         else:
2089             game.battle.invalidate()
2090     if game.idebug:
2091         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2092         for i in range(1, NEVENTS):
2093             if   i == FSNOVA:  proutn("=== Supernova       ")
2094             elif i == FTBEAM:  proutn("=== T Beam          ")
2095             elif i == FSNAP:   proutn("=== Snapshot        ")
2096             elif i == FBATTAK: proutn("=== Base Attack     ")
2097             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2098             elif i == FSCMOVE: proutn("=== SC Move         ")
2099             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2100             elif i == FDSPROB: proutn("=== Probe Move      ")
2101             elif i == FDISTR:  proutn("=== Distress Call   ")
2102             elif i == FENSLV:  proutn("=== Enslavement     ")
2103             elif i == FREPRO:  proutn("=== Klingon Build   ")
2104             if is_scheduled(i):
2105                 prout("%.2f" % (scheduled(i)))
2106             else:
2107                 prout("never")
2108     radio_was_broken = damaged(DRADIO)
2109     hold.i = hold.j = 0
2110     while True:
2111         # Select earliest extraneous event, evcode==0 if no events 
2112         evcode = FSPY
2113         if game.alldone:
2114             return
2115         datemin = fintim
2116         for l in range(1, NEVENTS):
2117             if game.future[l].date < datemin:
2118                 evcode = l
2119                 if game.idebug:
2120                     prout("== Event %d fires" % evcode)
2121                 datemin = game.future[l].date
2122         xtime = datemin-game.state.date
2123         game.state.date = datemin
2124         # Decrement Federation resources and recompute remaining time 
2125         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2126         game.recompute()
2127         if game.state.remtime <= 0:
2128             finish(FDEPLETE)
2129             return
2130         # Any crew left alive? 
2131         if game.state.crew <= 0:
2132             finish(FCREW)
2133             return
2134         # Is life support adequate? 
2135         if damaged(DLIFSUP) and game.condition != "docked":
2136             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2137                 finish(FLIFESUP)
2138                 return
2139             game.lsupres -= xtime
2140             if game.damage[DLIFSUP] <= xtime:
2141                 game.lsupres = game.inlsr
2142         # Fix devices 
2143         repair = xtime
2144         if game.condition == "docked":
2145             repair /= DOCKFAC
2146         # Don't fix Deathray here 
2147         for l in range(NDEVICES):
2148             if game.damage[l] > 0.0 and l != DDRAY:
2149                 if game.damage[l]-repair > 0.0:
2150                     game.damage[l] -= repair
2151                 else:
2152                     game.damage[l] = 0.0
2153         # If radio repaired, update star chart and attack reports 
2154         if radio_was_broken and not damaged(DRADIO):
2155             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2156             prout(_("   surveillance reports are coming in."))
2157             skip(1)
2158             if not game.iseenit:
2159                 attackreport(False)
2160                 game.iseenit = True
2161             rechart()
2162             prout(_("   The star chart is now up to date.\""))
2163             skip(1)
2164         # Cause extraneous event EVCODE to occur 
2165         game.optime -= xtime
2166         if evcode == FSNOVA: # Supernova 
2167             announce()
2168             supernova(None)
2169             schedule(FSNOVA, expran(0.5*game.intime))
2170             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2171                 return
2172         elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
2173             if game.state.nscrem == 0 or \
2174                 ictbeam or istract or \
2175                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2176                 return
2177             if game.ientesc or \
2178                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2179                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2180                 (damaged(DSHIELD) and \
2181                  (game.energy < 2500 or damaged(DPHASER)) and \
2182                  (game.torps < 5 or damaged(DPHOTON))):
2183                 # Tractor-beam her! 
2184                 istract = ictbeam = True
2185                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2186             else:
2187                 return
2188         elif evcode == FTBEAM: # Tractor beam 
2189             if not game.state.kcmdr:
2190                 unschedule(FTBEAM)
2191                 continue
2192             i = randrange(len(game.state.kcmdr))
2193             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2194             if istract or game.condition == "docked" or yank == 0:
2195                 # Drats! Have to reschedule 
2196                 schedule(FTBEAM, 
2197                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2198                 continue
2199             ictbeam = True
2200             tractorbeam(yank)
2201         elif evcode == FSNAP: # Snapshot of the universe (for time warp) 
2202             game.snapsht = copy.deepcopy(game.state)
2203             game.state.snap = True
2204             schedule(FSNAP, expran(0.5 * game.intime))
2205         elif evcode == FBATTAK: # Commander attacks starbase 
2206             if not game.state.kcmdr or not game.state.baseq:
2207                 # no can do 
2208                 unschedule(FBATTAK)
2209                 unschedule(FCDBAS)
2210                 continue
2211             try:
2212                 for ibq in game.state.baseq:
2213                     for cmdr in game.state.kcmdr: 
2214                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2215                             raise JumpOut
2216                 else:
2217                     # no match found -- try later 
2218                     schedule(FBATTAK, expran(0.3*game.intime))
2219                     unschedule(FCDBAS)
2220                     continue
2221             except JumpOut:
2222                 pass
2223             # commander + starbase combination found -- launch attack 
2224             game.battle = ibq
2225             schedule(FCDBAS, randreal(1.0, 4.0))
2226             if game.isatb: # extra time if SC already attacking 
2227                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2228             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2229             game.iseenit = False
2230             if not communicating():
2231                 continue # No warning :-( 
2232             game.iseenit = True
2233             announce()
2234             skip(1)
2235             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2236             prout(_("   reports that it is under attack and that it can"))
2237             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2238             if cancelrest():
2239                 return
2240         elif evcode == FSCDBAS: # Supercommander destroys base 
2241             unschedule(FSCDBAS)
2242             game.isatb = 2
2243             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase: 
2244                 continue # WAS RETURN! 
2245             hold = game.battle
2246             game.battle = game.state.kscmdr
2247             destroybase()
2248         elif evcode == FCDBAS: # Commander succeeds in destroying base 
2249             if evcode == FCDBAS:
2250                 unschedule(FCDBAS)
2251                 if not game.state.baseq() \
2252                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2253                     game.battle.invalidate()
2254                     continue
2255                 # find the lucky pair 
2256                 for cmdr in game.state.kcmdr:
2257                     if cmdr == game.battle: 
2258                         break
2259                 else:
2260                     # No action to take after all 
2261                     continue
2262             destroybase()
2263         elif evcode == FSCMOVE: # Supercommander moves 
2264             schedule(FSCMOVE, 0.2777)
2265             if not game.ientesc and not istract and game.isatb != 1 and \
2266                    (not game.iscate or not game.justin): 
2267                 supercommander()
2268         elif evcode == FDSPROB: # Move deep space probe 
2269             schedule(FDSPROB, 0.01)
2270             if not game.probe.next():
2271                 if not game.probe.quadrant().valid_quadrant() or \
2272                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2273                     # Left galaxy or ran into supernova
2274                     if communicating():
2275                         announce()
2276                         skip(1)
2277                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2278                         if not game.probe.quadrant().valid_quadrant():
2279                             prout(_("has left the galaxy.\""))
2280                         else:
2281                             prout(_("is no longer transmitting.\""))
2282                     unschedule(FDSPROB)
2283                     continue
2284                 if communicating():
2285                     #announce()
2286                     skip(1)
2287                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2288             pdest = game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j]
2289             if communicating():
2290                 chp = game.state.chart[game.probe.quadrant().i][game.probe.quadrant().j]
2291                 chp.klingons = pdest.klingons
2292                 chp.starbase = pdest.starbase
2293                 chp.stars = pdest.stars
2294                 pdest.charted = True
2295             game.probe.moves -= 1 # One less to travel
2296             if game.probe.arrived() and game.isarmed and pdest.stars:
2297                 supernova(game.probe)           # fire in the hole!
2298                 unschedule(FDSPROB)
2299                 if game.state.galaxy[game.quadrant().i][game.quadrant().j].supernova: 
2300                     return
2301         elif evcode == FDISTR: # inhabited system issues distress call 
2302             unschedule(FDISTR)
2303             # try a whole bunch of times to find something suitable 
2304             for i in range(100):
2305                 # need a quadrant which is not the current one,
2306                 # which has some stars which are inhabited and
2307                 # not already under attack, which is not
2308                 # supernova'ed, and which has some Klingons in it
2309                 w = randplace(GALSIZE)
2310                 q = game.state.galaxy[w.i][w.j]
2311                 if not (game.quadrant == w or q.planet == None or \
2312                       not q.planet.inhabited or \
2313                       q.supernova or q.status!="secure" or q.klingons<=0):
2314                     break
2315             else:
2316                 # can't seem to find one; ignore this call 
2317                 if game.idebug:
2318                     prout("=== Couldn't find location for distress event.")
2319                 continue
2320             # got one!!  Schedule its enslavement 
2321             ev = schedule(FENSLV, expran(game.intime))
2322             ev.quadrant = w
2323             q.status = "distressed"
2324             # tell the captain about it if we can 
2325             if communicating():
2326                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2327                         % (q.planet, repr(w)))
2328                 prout(_("by a Klingon invasion fleet."))
2329                 if cancelrest():
2330                     return
2331         elif evcode == FENSLV:          # starsystem is enslaved 
2332             ev = unschedule(FENSLV)
2333             # see if current distress call still active 
2334             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2335             if q.klingons <= 0:
2336                 q.status = "secure"
2337                 continue
2338             q.status = "enslaved"
2339
2340             # play stork and schedule the first baby 
2341             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2342             ev2.quadrant = ev.quadrant
2343
2344             # report the disaster if we can 
2345             if communicating():
2346                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2347                         q.planet)
2348                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2349         elif evcode == FREPRO:          # Klingon reproduces 
2350             # If we ever switch to a real event queue, we'll need to
2351             # explicitly retrieve and restore the x and y.
2352             ev = schedule(FREPRO, expran(1.0 * game.intime))
2353             # see if current distress call still active 
2354             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2355             if q.klingons <= 0:
2356                 q.status = "secure"
2357                 continue
2358             if game.state.remkl >= MAXKLGAME:
2359                 continue                # full right now 
2360             # reproduce one Klingon 
2361             w = ev.quadrant
2362             m = Coord()
2363             if game.klhere >= MAXKLQUAD:
2364                 try:
2365                     # this quadrant not ok, pick an adjacent one 
2366                     for m.i in range(w.i - 1, w.i + 2):
2367                         for m.j in range(w.j - 1, w.j + 2):
2368                             if not m.valid_quadrant():
2369                                 continue
2370                             q = game.state.galaxy[m.i][m.j]
2371                             # check for this quad ok (not full & no snova) 
2372                             if q.klingons >= MAXKLQUAD or q.supernova:
2373                                 continue
2374                             raise JumpOut
2375                     else:
2376                         continue        # search for eligible quadrant failed
2377                 except JumpOut:
2378                     w = m
2379             # deliver the child 
2380             game.state.remkl += 1
2381             q.klingons += 1
2382             if game.quadrant == w:
2383                 game.klhere += 1
2384                 game.enemies.append(newkling())
2385             # recompute time left
2386             game.recompute()
2387             if communicating():
2388                 if game.quadrant == w:
2389                     prout(_("Spock- sensors indicate the Klingons have"))
2390                     prout(_("launched a warship from %s.") % q.planet)
2391                 else:
2392                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2393                     if q.planet != None:
2394                         proutn(_("near %s ") % q.planet)
2395                     prout(_("in Quadrant %s.") % w)
2396                                 
2397 def wait():
2398     "Wait on events."
2399     game.ididit = False
2400     while True:
2401         key = scanner.next()
2402         if key  != "IHEOL":
2403             break
2404         proutn(_("How long? "))
2405     scanner.chew()
2406     if key != "IHREAL":
2407         huh()
2408         return
2409     origTime = delay = scanner.real
2410     if delay <= 0.0:
2411         return
2412     if delay >= game.state.remtime or len(game.enemies) != 0:
2413         proutn(_("Are you sure? "))
2414         if not ja():
2415             return
2416     # Alternate resting periods (events) with attacks 
2417     game.resting = True
2418     while True:
2419         if delay <= 0:
2420             game.resting = False
2421         if not game.resting:
2422             prout(_("%d stardates left.") % int(game.state.remtime))
2423             return
2424         temp = game.optime = delay
2425         if len(game.enemies):
2426             rtime = randreal(1.0, 2.0)
2427             if rtime < temp:
2428                 temp = rtime
2429             game.optime = temp
2430         if game.optime < delay:
2431             attack(torps_ok=False)
2432         if game.alldone:
2433             return
2434         events()
2435         game.ididit = True
2436         if game.alldone:
2437             return
2438         delay -= temp
2439         # Repair Deathray if long rest at starbase 
2440         if origTime-delay >= 9.99 and game.condition == "docked":
2441             game.damage[DDRAY] = 0.0
2442         # leave if quadrant supernovas
2443         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2444             break
2445     game.resting = False
2446     game.optime = 0
2447
2448 def nova(nov):
2449     "Star goes nova." 
2450     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2451     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2452     if withprob(0.05):
2453         # Wow! We've supernova'ed 
2454         supernova(game.quadrant)
2455         return
2456     # handle initial nova 
2457     game.quad[nov.i][nov.j] = '.'
2458     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2459     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2460     game.state.starkl += 1
2461     # Set up queue to recursively trigger adjacent stars 
2462     hits = [nov]
2463     kount = 0
2464     while hits:
2465         offset = Coord()
2466         start = hits.pop()
2467         for offset.i in range(-1, 1+1):
2468             for offset.j in range(-1, 1+1):
2469                 if offset.j == 0 and offset.i == 0:
2470                     continue
2471                 neighbor = start + offset
2472                 if not neighbor.valid_sector():
2473                     continue
2474                 iquad = game.quad[neighbor.i][neighbor.j]
2475                 # Empty space ends reaction
2476                 if iquad in ('.', '?', ' ', 'T', '#'):
2477                     pass
2478                 elif iquad == '*': # Affect another star 
2479                     if withprob(0.05):
2480                         # This star supernovas 
2481                         supernova(game.quadrant)
2482                         return
2483                     else:
2484                         hits.append(neighbor)
2485                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2486                         game.state.starkl += 1
2487                         proutn(crmena(True, '*', "sector", neighbor))
2488                         prout(_(" novas."))
2489                         game.quad[neighbor.i][neighbor.j] = '.'
2490                         kount += 1
2491                 elif iquad in ('P', '@'): # Destroy planet 
2492                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2493                     if iquad == 'P':
2494                         game.state.nplankl += 1
2495                     else:
2496                         game.state.nworldkl += 1
2497                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2498                     game.iplnet.pclass = "destroyed"
2499                     game.iplnet = None
2500                     game.plnet.invalidate()
2501                     if game.landed:
2502                         finish(FPNOVA)
2503                         return
2504                     game.quad[neighbor.i][neighbor.j] = '.'
2505                 elif iquad == 'B': # Destroy base 
2506                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2507                     game.state.baseq = filter(lambda x: x!= game.quadrant, game.state.baseq)
2508                     game.base.invalidate()
2509                     game.state.basekl += 1
2510                     newcnd()
2511                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2512                     game.quad[neighbor.i][neighbor.j] = '.'
2513                 elif iquad in ('E', 'F'): # Buffet ship 
2514                     prout(_("***Starship buffeted by nova."))
2515                     if game.shldup:
2516                         if game.shield >= 2000.0:
2517                             game.shield -= 2000.0
2518                         else:
2519                             diff = 2000.0 - game.shield
2520                             game.energy -= diff
2521                             game.shield = 0.0
2522                             game.shldup = False
2523                             prout(_("***Shields knocked out."))
2524                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2525                     else:
2526                         game.energy -= 2000.0
2527                     if game.energy <= 0:
2528                         finish(FNOVA)
2529                         return
2530                     # add in course nova contributes to kicking starship
2531                     bump += (game.sector-hits[-1]).sgn()
2532                 elif iquad == 'K': # kill klingon 
2533                     deadkl(neighbor, iquad, neighbor)
2534                 elif iquad in ('C','S','R'): # Damage/destroy big enemies 
2535                     for ll in range(len(game.enemies)):
2536                         if game.enemies[ll].location == neighbor:
2537                             break
2538                     game.enemies[ll].power -= 800.0 # If firepower is lost, die 
2539                     if game.enemies[ll].power <= 0.0:
2540                         deadkl(neighbor, iquad, neighbor)
2541                         break
2542                     newc = neighbor + neighbor - hits[-1]
2543                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2544                     if not newc.valid_sector():
2545                         # can't leave quadrant 
2546                         skip(1)
2547                         break
2548                     iquad1 = game.quad[newc.i][newc.j]
2549                     if iquad1 == ' ':
2550                         proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2551                         skip(1)
2552                         deadkl(neighbor, iquad, newc)
2553                         break
2554                     if iquad1 != '.':
2555                         # can't move into something else 
2556                         skip(1)
2557                         break
2558                     proutn(_(", buffeted to Sector %s") % newc)
2559                     game.quad[neighbor.i][neighbor.j] = '.'
2560                     game.quad[newc.i][newc.j] = iquad
2561                     game.enemies[ll].move(newc)
2562     # Starship affected by nova -- kick it away. 
2563     dist = kount*0.1
2564     direc = ncourse[3*(bump.i+1)+bump.j+2]
2565     if direc == 0.0:
2566         dist = 0.0
2567     if dist == 0.0:
2568         return
2569     scourse = course(bearing=direc, distance=dist)
2570     game.optime = scourse.time(warp=4)
2571     skip(1)
2572     prout(_("Force of nova displaces starship."))
2573     imove(scourse, noattack=True)
2574     game.optime = scourse.time(warp=4)
2575     return
2576         
2577 def supernova(w):
2578     "Star goes supernova."
2579     num = 0; npdead = 0
2580     if w != None: 
2581         nq = copy.copy(w)
2582     else:
2583         # Scheduled supernova -- select star at random. 
2584         stars = 0
2585         nq = Coord()
2586         for nq.i in range(GALSIZE):
2587             for nq.j in range(GALSIZE):
2588                 stars += game.state.galaxy[nq.i][nq.j].stars
2589         if stars == 0:
2590             return # nothing to supernova exists 
2591         num = randrange(stars) + 1
2592         for nq.i in range(GALSIZE):
2593             for nq.j in range(GALSIZE):
2594                 num -= game.state.galaxy[nq.i][nq.j].stars
2595                 if num <= 0:
2596                     break
2597             if num <=0:
2598                 break
2599         if game.idebug:
2600             proutn("=== Super nova here?")
2601             if ja():
2602                 nq = game.quadrant
2603     if not nq == game.quadrant or game.justin:
2604         # it isn't here, or we just entered (treat as enroute) 
2605         if communicating():
2606             skip(1)
2607             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2608             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2609     else:
2610         ns = Coord()
2611         # we are in the quadrant! 
2612         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2613         for ns.i in range(QUADSIZE):
2614             for ns.j in range(QUADSIZE):
2615                 if game.quad[ns.i][ns.j]=='*':
2616                     num -= 1
2617                     if num==0:
2618                         break
2619             if num==0:
2620                 break
2621         skip(1)
2622         prouts(_("***RED ALERT!  RED ALERT!"))
2623         skip(1)
2624         prout(_("***Incipient supernova detected at Sector %s") % ns)
2625         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2626             proutn(_("Emergency override attempts t"))
2627             prouts("***************")
2628             skip(1)
2629             stars()
2630             game.alldone = True
2631     # destroy any Klingons in supernovaed quadrant
2632     kldead = game.state.galaxy[nq.i][nq.j].klingons
2633     game.state.galaxy[nq.i][nq.j].klingons = 0
2634     if nq == game.state.kscmdr:
2635         # did in the Supercommander! 
2636         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2637         game.iscate = False
2638         unschedule(FSCMOVE)
2639         unschedule(FSCDBAS)
2640     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2641     comkills = len(game.state.kcmdr) - len(survivors)
2642     game.state.kcmdr = survivors
2643     kldead -= comkills
2644     if not game.state.kcmdr:
2645         unschedule(FTBEAM)
2646     game.state.remkl -= kldead
2647     # destroy Romulans and planets in supernovaed quadrant 
2648     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2649     game.state.galaxy[nq.i][nq.j].romulans = 0
2650     game.state.nromrem -= nrmdead
2651     # Destroy planets 
2652     for loop in range(game.inplan):
2653         if game.state.planets[loop].quadrant == nq:
2654             game.state.planets[loop].pclass = "destroyed"
2655             npdead += 1
2656     # Destroy any base in supernovaed quadrant
2657     game.state.baseq = filter(lambda x: x != nq, game.state.baseq)
2658     # If starship caused supernova, tally up destruction 
2659     if w != None:
2660         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2661         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2662         game.state.nplankl += npdead
2663     # mark supernova in galaxy and in star chart 
2664     if game.quadrant == nq or communicating():
2665         game.state.galaxy[nq.i][nq.j].supernova = True
2666     # If supernova destroys last Klingons give special message 
2667     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2668         skip(2)
2669         if w == None:
2670             prout(_("Lucky you!"))
2671         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2672         finish(FWON)
2673         return
2674     # if some Klingons remain, continue or die in supernova 
2675     if game.alldone:
2676         finish(FSNOVAED)
2677     return
2678
2679 # Code from finish.c ends here.
2680
2681 def selfdestruct():
2682     "Self-destruct maneuver. Finish with a BANG!" 
2683     scanner.chew()
2684     if damaged(DCOMPTR):
2685         prout(_("Computer damaged; cannot execute destruct sequence."))
2686         return
2687     prouts(_("---WORKING---")); skip(1)
2688     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2689     prouts("   10"); skip(1)
2690     prouts("       9"); skip(1)
2691     prouts("          8"); skip(1)
2692     prouts("             7"); skip(1)
2693     prouts("                6"); skip(1)
2694     skip(1)
2695     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2696     skip(1)
2697     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2698     skip(1)
2699     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2700     skip(1)
2701     scanner.next()
2702     if game.passwd != scanner.token:
2703         prouts(_("PASSWORD-REJECTED;"))
2704         skip(1)
2705         prouts(_("CONTINUITY-EFFECTED"))
2706         skip(2)
2707         return
2708     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2709     prouts("                   5"); skip(1)
2710     prouts("                      4"); skip(1)
2711     prouts("                         3"); skip(1)
2712     prouts("                            2"); skip(1)
2713     prouts("                              1"); skip(1)
2714     if withprob(0.15):
2715         prouts(_("GOODBYE-CRUEL-WORLD"))
2716         skip(1)
2717     kaboom()
2718
2719 def kaboom():
2720     stars()
2721     if game.ship=='E':
2722         prouts("***")
2723     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2724     skip(1)
2725     stars()
2726     skip(1)
2727     if len(game.enemies) != 0:
2728         whammo = 25.0 * game.energy
2729         for l in range(len(game.enemies)):
2730             if game.enemies[l].power*game.enemies[l].kdist <= whammo: 
2731                 deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
2732     finish(FDILITHIUM)
2733                                 
2734 def killrate():
2735     "Compute our rate of kils over time."
2736     elapsed = game.state.date - game.indate
2737     if elapsed == 0:    # Avoid divide-by-zero error if calculated on turn 0
2738         return 0
2739     else:
2740         starting = (game.inkling + game.incom + game.inscom)
2741         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2742         return (starting - remaining)/elapsed
2743
2744 def badpoints():
2745     "Compute demerits."
2746     badpt = 5.0*game.state.starkl + \
2747             game.casual + \
2748             10.0*game.state.nplankl + \
2749             300*game.state.nworldkl + \
2750             45.0*game.nhelp +\
2751             100.0*game.state.basekl +\
2752             3.0*game.abandoned
2753     if game.ship == 'F':
2754         badpt += 100.0
2755     elif game.ship == None:
2756         badpt += 200.0
2757     return badpt
2758
2759 def finish(ifin):
2760     # end the game, with appropriate notfications 
2761     igotit = False
2762     game.alldone = True
2763     skip(3)
2764     prout(_("It is stardate %.1f.") % game.state.date)
2765     skip(1)
2766     if ifin == FWON: # Game has been won
2767         if game.state.nromrem != 0:
2768             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2769                   game.state.nromrem)
2770
2771         prout(_("You have smashed the Klingon invasion fleet and saved"))
2772         prout(_("the Federation."))
2773         game.gamewon = True
2774         if game.alive:
2775             badpt = badpoints()
2776             if badpt < 100.0:
2777                 badpt = 0.0     # Close enough!
2778             # killsPerDate >= RateMax
2779             if game.state.date-game.indate < 5.0 or \
2780                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2781                 skip(1)
2782                 prout(_("In fact, you have done so well that Starfleet Command"))
2783                 if game.skill == SKILL_NOVICE:
2784                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2785                 elif game.skill == SKILL_FAIR:
2786                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2787                 elif game.skill == SKILL_GOOD:
2788                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2789                 elif game.skill == SKILL_EXPERT:
2790                     prout(_("promotes you to Commodore Emeritus."))
2791                     skip(1)
2792                     prout(_("Now that you think you're really good, try playing"))
2793                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2794                 elif game.skill == SKILL_EMERITUS:
2795                     skip(1)
2796                     proutn(_("Computer-  "))
2797                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2798                     skip(2)
2799                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2800                     skip(1)
2801                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2802                     skip(1)
2803                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2804                     skip(1)
2805                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2806                     skip(1)
2807                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2808                     skip(2)
2809                     prout(_("Now you can retire and write your own Star Trek game!"))
2810                     skip(1)
2811                 elif game.skill >= SKILL_EXPERT:
2812                     if game.thawed and not game.idebug:
2813                         prout(_("You cannot get a citation, so..."))
2814                     else:
2815                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
2816                         scanner.chew()
2817                         if ja():
2818                             igotit = True
2819             # Only grant long life if alive (original didn't!)
2820             skip(1)
2821             prout(_("LIVE LONG AND PROSPER."))
2822         score()
2823         if igotit:
2824             plaque()        
2825         return
2826     elif ifin == FDEPLETE: # Federation Resources Depleted
2827         prout(_("Your time has run out and the Federation has been"))
2828         prout(_("conquered.  Your starship is now Klingon property,"))
2829         prout(_("and you are put on trial as a war criminal.  On the"))
2830         proutn(_("basis of your record, you are "))
2831         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
2832             prout(_("acquitted."))
2833             skip(1)
2834             prout(_("LIVE LONG AND PROSPER."))
2835         else:
2836             prout(_("found guilty and"))
2837             prout(_("sentenced to death by slow torture."))
2838             game.alive = False
2839         score()
2840         return
2841     elif ifin == FLIFESUP:
2842         prout(_("Your life support reserves have run out, and"))
2843         prout(_("you die of thirst, starvation, and asphyxiation."))
2844         prout(_("Your starship is a derelict in space."))
2845     elif ifin == FNRG:
2846         prout(_("Your energy supply is exhausted."))
2847         skip(1)
2848         prout(_("Your starship is a derelict in space."))
2849     elif ifin == FBATTLE:
2850         prout(_("The %s has been destroyed in battle.") % crmshp())
2851         skip(1)
2852         prout(_("Dulce et decorum est pro patria mori."))
2853     elif ifin == FNEG3:
2854         prout(_("You have made three attempts to cross the negative energy"))
2855         prout(_("barrier which surrounds the galaxy."))
2856         skip(1)
2857         prout(_("Your navigation is abominable."))
2858         score()
2859     elif ifin == FNOVA:
2860         prout(_("Your starship has been destroyed by a nova."))
2861         prout(_("That was a great shot."))
2862         skip(1)
2863     elif ifin == FSNOVAED:
2864         prout(_("The %s has been fried by a supernova.") % crmshp())
2865         prout(_("...Not even cinders remain..."))
2866     elif ifin == FABANDN:
2867         prout(_("You have been captured by the Klingons. If you still"))
2868         prout(_("had a starbase to be returned to, you would have been"))
2869         prout(_("repatriated and given another chance. Since you have"))
2870         prout(_("no starbases, you will be mercilessly tortured to death."))
2871     elif ifin == FDILITHIUM:
2872         prout(_("Your starship is now an expanding cloud of subatomic particles"))
2873     elif ifin == FMATERIALIZE:
2874         prout(_("Starbase was unable to re-materialize your starship."))
2875         prout(_("Sic transit gloria mundi"))
2876     elif ifin == FPHASER:
2877         prout(_("The %s has been cremated by its own phasers.") % crmshp())
2878     elif ifin == FLOST:
2879         prout(_("You and your landing party have been"))
2880         prout(_("converted to energy, disipating through space."))
2881     elif ifin == FMINING:
2882         prout(_("You are left with your landing party on"))
2883         prout(_("a wild jungle planet inhabited by primitive cannibals."))
2884         skip(1)
2885         prout(_("They are very fond of \"Captain Kirk\" soup."))
2886         skip(1)
2887         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2888     elif ifin == FDPLANET:
2889         prout(_("You and your mining party perish."))
2890         skip(1)
2891         prout(_("That was a great shot."))
2892         skip(1)
2893     elif ifin == FSSC:
2894         prout(_("The Galileo is instantly annihilated by the supernova."))
2895         prout(_("You and your mining party are atomized."))
2896         skip(1)
2897         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2898         prout(_("joins the Romulans, wreaking terror on the Federation."))
2899     elif ifin == FPNOVA:
2900         prout(_("You and your mining party are atomized."))
2901         skip(1)
2902         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2903         prout(_("joins the Romulans, wreaking terror on the Federation."))
2904     elif ifin == FSTRACTOR:
2905         prout(_("The shuttle craft Galileo is also caught,"))
2906         prout(_("and breaks up under the strain."))
2907         skip(1)
2908         prout(_("Your debris is scattered for millions of miles."))
2909         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2910     elif ifin == FDRAY:
2911         prout(_("The mutants attack and kill Spock."))
2912         prout(_("Your ship is captured by Klingons, and"))
2913         prout(_("your crew is put on display in a Klingon zoo."))
2914     elif ifin == FTRIBBLE:
2915         prout(_("Tribbles consume all remaining water,"))
2916         prout(_("food, and oxygen on your ship."))
2917         skip(1)
2918         prout(_("You die of thirst, starvation, and asphyxiation."))
2919         prout(_("Your starship is a derelict in space."))
2920     elif ifin == FHOLE:
2921         prout(_("Your ship is drawn to the center of the black hole."))
2922         prout(_("You are crushed into extremely dense matter."))
2923     elif ifin == FCREW:
2924         prout(_("Your last crew member has died."))
2925     if game.ship == 'F':
2926         game.ship = None
2927     elif game.ship == 'E':
2928         game.ship = 'F'
2929     game.alive = False
2930     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
2931         goodies = game.state.remres/game.inresor
2932         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
2933         if goodies/baddies >= randreal(1.0, 1.5):
2934             prout(_("As a result of your actions, a treaty with the Klingon"))
2935             prout(_("Empire has been signed. The terms of the treaty are"))
2936             if goodies/baddies >= randreal(3.0):
2937                 prout(_("favorable to the Federation."))
2938                 skip(1)
2939                 prout(_("Congratulations!"))
2940             else:
2941                 prout(_("highly unfavorable to the Federation."))
2942         else:
2943             prout(_("The Federation will be destroyed."))
2944     else:
2945         prout(_("Since you took the last Klingon with you, you are a"))
2946         prout(_("martyr and a hero. Someday maybe they'll erect a"))
2947         prout(_("statue in your memory. Rest in peace, and try not"))
2948         prout(_("to think about pigeons."))
2949         game.gamewon = True
2950     score()
2951
2952 def score():
2953     "Compute player's score."
2954     timused = game.state.date - game.indate
2955     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
2956         timused = 5.0
2957     game.perdate = killrate()
2958     ithperd = 500*game.perdate + 0.5
2959     iwon = 0
2960     if game.gamewon:
2961         iwon = 100*game.skill
2962     if game.ship == 'E': 
2963         klship = 0
2964     elif game.ship == 'F': 
2965         klship = 1
2966     else:
2967         klship = 2
2968     game.score = 10*(game.inkling - game.state.remkl) \
2969              + 50*(game.incom - len(game.state.kcmdr)) \
2970              + ithperd + iwon \
2971              + 20*(game.inrom - game.state.nromrem) \
2972              + 200*(game.inscom - game.state.nscrem) \
2973              - game.state.nromrem \
2974              - badpoints()
2975     if not game.alive:
2976         game.score -= 200
2977     skip(2)
2978     prout(_("Your score --"))
2979     if game.inrom - game.state.nromrem:
2980         prout(_("%6d Romulans destroyed                 %5d") %
2981               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
2982     if game.state.nromrem and game.gamewon:
2983         prout(_("%6d Romulans captured                  %5d") %
2984               (game.state.nromrem, game.state.nromrem))
2985     if game.inkling - game.state.remkl:
2986         prout(_("%6d ordinary Klingons destroyed        %5d") %
2987               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
2988     if game.incom - len(game.state.kcmdr):
2989         prout(_("%6d Klingon commanders destroyed       %5d") %
2990               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
2991     if game.inscom - game.state.nscrem:
2992         prout(_("%6d Super-Commander destroyed          %5d") %
2993               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
2994     if ithperd:
2995         prout(_("%6.2f Klingons per stardate              %5d") %
2996               (game.perdate, ithperd))
2997     if game.state.starkl:
2998         prout(_("%6d stars destroyed by your action     %5d") %
2999               (game.state.starkl, -5*game.state.starkl))
3000     if game.state.nplankl:
3001         prout(_("%6d planets destroyed by your action   %5d") %
3002               (game.state.nplankl, -10*game.state.nplankl))
3003     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3004         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3005               (game.state.nworldkl, -300*game.state.nworldkl))
3006     if game.state.basekl:
3007         prout(_("%6d bases destroyed by your action     %5d") %
3008               (game.state.basekl, -100*game.state.basekl))
3009     if game.nhelp:
3010         prout(_("%6d calls for help from starbase       %5d") %
3011               (game.nhelp, -45*game.nhelp))
3012     if game.casual:
3013         prout(_("%6d casualties incurred                %5d") %
3014               (game.casual, -game.casual))
3015     if game.abandoned:
3016         prout(_("%6d crew abandoned in space            %5d") %
3017               (game.abandoned, -3*game.abandoned))
3018     if klship:
3019         prout(_("%6d ship(s) lost or destroyed          %5d") %
3020               (klship, -100*klship))
3021     if not game.alive:
3022         prout(_("Penalty for getting yourself killed        -200"))
3023     if game.gamewon:
3024         proutn(_("Bonus for winning "))
3025         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3026         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3027         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3028         elif game.skill ==  SKILL_EXPERT:       proutn(_("Expert game  "))
3029         elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
3030         prout("           %5d" % iwon)
3031     skip(1)
3032     prout(_("TOTAL SCORE                               %5d") % game.score)
3033
3034 def plaque():
3035     "Emit winner's commemmorative plaque." 
3036     skip(2)
3037     while True:
3038         proutn(_("File or device name for your plaque: "))
3039         winner = cgetline()
3040         try:
3041             fp = open(winner, "w")
3042             break
3043         except IOError:
3044             prout(_("Invalid name."))
3045
3046     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3047     winner = cgetline()
3048     # The 38 below must be 64 for 132-column paper 
3049     nskip = 38 - len(winner)/2
3050     fp.write("\n\n\n\n")
3051     # --------DRAW ENTERPRISE PICTURE. 
3052     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3053     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3054     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3055     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3056     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3057     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3058     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3059     fp.write("                                      EEE           E          E            E  E\n")
3060     fp.write("                                                       E         E          E  E\n")
3061     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3062     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3063     fp.write("                                                    :E    :                 EEEE       E\n")
3064     fp.write("                                                   .-E   -:-----                       E\n")
3065     fp.write("                                                    :E    :                            E\n")
3066     fp.write("                                                      EE  :                    EEEEEEEE\n")
3067     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3068     fp.write("\n\n\n")
3069     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3070     fp.write("\n\n\n\n")
3071     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3072     fp.write("\n")
3073     fp.write(_("                                                Starfleet Command bestows to you\n"))
3074     fp.write("\n")
3075     fp.write("%*s%s\n\n" % (nskip, "", winner))
3076     fp.write(_("                                                           the rank of\n\n"))
3077     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3078     fp.write("                                                          ")
3079     if game.skill ==  SKILL_EXPERT:
3080         fp.write(_(" Expert level\n\n"))
3081     elif game.skill == SKILL_EMERITUS:
3082         fp.write(_("Emeritus level\n\n"))
3083     else:
3084         fp.write(_(" Cheat level\n\n"))
3085     timestring = time.ctime()
3086     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3087                     (timestring+4, timestring+20, timestring+11))
3088     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3089     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3090     fp.close()
3091
3092 # Code from io.c begins here
3093
3094 rows = linecount = 0    # for paging 
3095 stdscr = None
3096 replayfp = None
3097 fullscreen_window = None
3098 srscan_window     = None
3099 report_window     = None
3100 status_window     = None
3101 lrscan_window     = None
3102 message_window    = None
3103 prompt_window     = None
3104 curwnd = None
3105
3106 def iostart():
3107     global stdscr, rows
3108     "for some recent versions of python2, the following enables UTF8"
3109     "for the older ones we probably need to set C locale, and the python3"
3110     "has no problems at all"
3111     if sys.version_info[0] < 3:
3112         import locale
3113         locale.setlocale(locale.LC_ALL, "")
3114     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3115     gettext.textdomain("sst")
3116     if not (game.options & OPTION_CURSES):
3117         ln_env = os.getenv("LINES")
3118         if ln_env:
3119             rows = ln_env
3120         else:
3121             rows = 25
3122     else:
3123         stdscr = curses.initscr()
3124         stdscr.keypad(True)
3125         curses.nonl()
3126         curses.cbreak()
3127         if game.options & OPTION_COLOR:
3128             curses.start_color()
3129             curses.use_default_colors()
3130             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3131             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3132             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3133             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3134             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3135             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3136             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3137             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3138         global fullscreen_window, srscan_window, report_window, status_window
3139         global lrscan_window, message_window, prompt_window
3140         (rows, columns)   = stdscr.getmaxyx()
3141         fullscreen_window = stdscr
3142         srscan_window     = curses.newwin(12, 25, 0,       0)
3143         report_window     = curses.newwin(11, 0,  1,       25)
3144         status_window     = curses.newwin(10, 0,  1,       39)
3145         lrscan_window     = curses.newwin(5,  0,  0,       64) 
3146         message_window    = curses.newwin(0,  0,  12,      0)
3147         prompt_window     = curses.newwin(1,  0,  rows-2,  0) 
3148         message_window.scrollok(True)
3149         setwnd(fullscreen_window)
3150
3151 def ioend():
3152     "Wrap up I/O."
3153     if game.options & OPTION_CURSES:
3154         stdscr.keypad(False)
3155         curses.echo()
3156         curses.nocbreak()
3157         curses.endwin()
3158
3159 def waitfor():
3160     "Wait for user action -- OK to do nothing if on a TTY"
3161     if game.options & OPTION_CURSES:
3162         stdscr.getch()
3163
3164 def announce():
3165     skip(1)
3166     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3167     skip(1)
3168
3169 def pause_game():
3170     if game.skill > SKILL_FAIR:
3171         prompt = _("[CONTINUE?]")
3172     else:
3173         prompt = _("[PRESS ENTER TO CONTINUE]")
3174
3175     if game.options & OPTION_CURSES:
3176         drawmaps(0)
3177         setwnd(prompt_window)
3178         prompt_window.clear()
3179         prompt_window.addstr(prompt)
3180         prompt_window.getstr()
3181         prompt_window.clear()
3182         prompt_window.refresh()
3183         setwnd(message_window)
3184     else:
3185         global linecount
3186         sys.stdout.write('\n')
3187         proutn(prompt)
3188         raw_input()
3189         sys.stdout.write('\n' * rows)
3190         linecount = 0
3191
3192 def skip(i):
3193     "Skip i lines.  Pause game if this would cause a scrolling event."
3194     for dummy in range(i):
3195         if game.options & OPTION_CURSES:
3196             (y, x) = curwnd.getyx()
3197             try:
3198                 curwnd.move(y+1, 0)
3199             except curses.error:
3200                 pass
3201         else:
3202             global linecount
3203             linecount += 1
3204             if rows and linecount >= rows:
3205                 pause_game()
3206             else:
3207                 sys.stdout.write('\n')
3208
3209 def proutn(line):
3210     "Utter a line with no following line feed."
3211     if game.options & OPTION_CURSES:
3212         (y, x) = curwnd.getyx()
3213         (my, mx) = curwnd.getmaxyx()
3214         if curwnd == message_window and y >= my - 2:
3215             pause_game()
3216             clrscr()
3217         # Uncomment this to debug curses problems
3218         if logfp:
3219             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(line)))
3220         curwnd.addstr(line)
3221         curwnd.refresh()
3222     else:
3223         sys.stdout.write(line)
3224         sys.stdout.flush()
3225
3226 def prout(line):
3227     proutn(line)
3228     skip(1)
3229
3230 def prouts(line):
3231     "Emit slowly!" 
3232     for c in line:
3233         if not replayfp or replayfp.closed:     # Don't slow down replays
3234             time.sleep(0.03)
3235         proutn(c)
3236         if game.options & OPTION_CURSES:
3237             curwnd.refresh()
3238         else:
3239             sys.stdout.flush()
3240     if not replayfp or replayfp.closed:
3241         time.sleep(0.03)
3242
3243 def cgetline():
3244     "Get a line of input."
3245     if game.options & OPTION_CURSES:
3246         line = curwnd.getstr() + "\n"
3247         curwnd.refresh()
3248     else:
3249         if replayfp and not replayfp.closed:
3250             while True:
3251                 line = replayfp.readline()
3252                 proutn(line)
3253                 if line == '':
3254                     prout("*** Replay finished")
3255                     replayfp.close()
3256                     break
3257                 elif line[0] != "#":
3258                     break
3259         else:
3260             line = raw_input() + "\n"
3261     if logfp:
3262         logfp.write(line)
3263     return line
3264
3265 def setwnd(wnd):
3266     "Change windows -- OK for this to be a no-op in tty mode."
3267     global curwnd
3268     if game.options & OPTION_CURSES:
3269         # Uncomment this to debug curses problems
3270         if logfp:
3271             if wnd == fullscreen_window:
3272                 legend = "fullscreen"
3273             elif wnd == srscan_window:
3274                 legend = "srscan"
3275             elif wnd == report_window:
3276                 legend = "report"
3277             elif wnd == status_window:
3278                 legend = "status"
3279             elif wnd == lrscan_window:
3280                 legend = "lrscan"
3281             elif wnd == message_window:
3282                 legend = "message"
3283             elif wnd == prompt_window:
3284                 legend = "prompt"
3285             else:
3286                 legend = "unknown"
3287             logfp.write("#curses: setwnd(%s)\n" % legend)
3288         curwnd = wnd
3289         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
3290
3291 def clreol():
3292     "Clear to end of line -- can be a no-op in tty mode" 
3293     if game.options & OPTION_CURSES:
3294         curwnd.clrtoeol()
3295         curwnd.refresh()
3296
3297 def clrscr():
3298     "Clear screen -- can be a no-op in tty mode."
3299     global linecount
3300     if game.options & OPTION_CURSES:
3301         curwnd.clear()
3302         curwnd.move(0, 0)
3303         curwnd.refresh()
3304     linecount = 0
3305
3306 def textcolor(color=DEFAULT):
3307     if game.options & OPTION_COLOR:
3308         if color == DEFAULT: 
3309             curwnd.attrset(0)
3310         elif color ==  BLACK: 
3311             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3312         elif color ==  BLUE: 
3313             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3314         elif color ==  GREEN: 
3315             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3316         elif color ==  CYAN: 
3317             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3318         elif color ==  RED: 
3319             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3320         elif color ==  MAGENTA: 
3321             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3322         elif color ==  BROWN: 
3323             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3324         elif color ==  LIGHTGRAY: 
3325             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3326         elif color ==  DARKGRAY: 
3327             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3328         elif color ==  LIGHTBLUE: 
3329             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3330         elif color ==  LIGHTGREEN: 
3331             curwnd.attron(curses.color_pair(curse