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