Yet more pychecker fixes. Down to 16 warnings now.
[super-star-trek.git] / sst.py
1 #!/usr/bin/env python
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 import os, sys, math, curses, time, readline, cPickle, random, copy, gettext, getpass
15
16 version="2.0"
17
18 docpath         = (".", "../doc", "/usr/share/doc/sst")
19
20 def _(str): return gettext.gettext(str)
21
22 GALSIZE         = 8             # Galaxy size in quadrants
23 NINHAB          = (GALSIZE * GALSIZE / 2)       # Number of inhabited worlds
24 MAXUNINHAB      = 10            # Maximum uninhabited worlds
25 QUADSIZE        = 10            # Quadrant size in sectors
26 BASEMIN         = 2                             # Minimum starbases
27 BASEMAX         = (GALSIZE * GALSIZE / 12)      # Maximum starbases
28 MAXKLGAME       = 127           # Maximum Klingons per game
29 MAXKLQUAD       = 9             # Maximum Klingons per quadrant
30 FULLCREW        = 428           # Crew size. BSD Trek was 387, that's wrong 
31 FOREVER         = 1e30          # Time for the indefinite future
32 MAXBURST        = 3             # Max # of torps you can launch in one turn
33 MINCMDR         = 10            # Minimum number of Klingon commanders
34 DOCKFAC         = 0.25          # Repair faster when docked
35 PHASEFAC        = 2.0           # Unclear what this is, it was in the C version
36
37 DEFAULT      = -1
38 BLACK        = 0
39 BLUE         = 1
40 GREEN        = 2
41 CYAN         = 3
42 RED          = 4
43 MAGENTA      = 5
44 BROWN        = 6
45 LIGHTGRAY    = 7
46 DARKGRAY     = 8
47 LIGHTBLUE    = 9
48 LIGHTGREEN   = 10
49 LIGHTCYAN    = 11
50 LIGHTRED     = 12
51 LIGHTMAGENTA = 13
52 YELLOW       = 14
53 WHITE        = 15
54
55 class TrekError(Exception):
56     pass
57
58 class JumpOut(Exception):
59     pass 
60
61 class Coord:
62     def __init__(self, x=None, y=None):
63         self.i = x
64         self.j = y
65     def valid_quadrant(self):
66         return self.i>=0 and self.i<GALSIZE and self.j>=0 and self.j<GALSIZE
67     def valid_sector(self):
68         return self.i>=0 and self.i<QUADSIZE and self.j>=0 and self.j<QUADSIZE
69     def invalidate(self):
70         self.i = self.j = None
71     def is_valid(self):
72         return self.i != None and self.j != None
73     def __eq__(self, other):
74         return other != None and self.i == other.i and self.j == other.j
75     def __ne__(self, other):
76         return other == None or self.i != other.i or self.j != other.j
77     def __add__(self, other):
78         return Coord(self.i+other.i, self.j+other.j)
79     def __sub__(self, other):
80         return Coord(self.i-other.i, self.j-other.j)
81     def __mul__(self, other):
82         return Coord(self.i*other, self.j*other)
83     def __rmul__(self, other):
84         return Coord(self.i*other, self.j*other)
85     def __div__(self, other):
86         return Coord(self.i/other, self.j/other)
87     def __mod__(self, other):
88         return Coord(self.i % other, self.j % other)
89     def __rdiv__(self, other):
90         return Coord(self.i/other, self.j/other)
91     def roundtogrid(self):
92         return Coord(int(round(self.i)), int(round(self.j)))
93     def distance(self, other=None):
94         if not other: other = Coord(0, 0)
95         return math.sqrt((self.i - other.i)**2 + (self.j - other.j)**2)
96     def bearing(self):
97         return 1.90985*math.atan2(self.j, self.i)
98     def sgn(self):
99         s = Coord()
100         if self.i == 0:
101             s.i = 0
102         else:
103             s.i = self.i / abs(self.i)
104         if self.j == 0:
105             s.j = 0
106         else:
107             s.j = self.j / abs(self.j)
108         return s
109     def quadrant(self):
110         #print "Location %s -> %s" % (self, (self / QUADSIZE).roundtogrid())
111         return self.roundtogrid() / QUADSIZE
112     def sector(self):
113         return self.roundtogrid() % QUADSIZE
114     def scatter(self):
115         s = Coord()
116         s.i = self.i + randrange(-1, 2)
117         s.j = self.j + randrange(-1, 2)
118         return s
119     def __str__(self):
120         if self.i == None or self.j == None:
121             return "Nowhere"
122         return "%s - %s" % (self.i+1, self.j+1)
123     __repr__ = __str__
124
125 class Planet:
126     def __init__(self):
127         self.name = None        # string-valued if inhabited
128         self.quadrant = Coord() # quadrant located
129         self.pclass = None      # could be ""M", "N", "O", or "destroyed"
130         self.crystals = "absent"# could be "mined", "present", "absent"
131         self.known = "unknown"  # could be "unknown", "known", "shuttle_down"
132         self.inhabited = False  # is it inhabites?
133     def __str__(self):
134         return self.name
135
136 class Quadrant:
137     def __init__(self):
138         self.stars = 0
139         self.planet = None
140         self.starbase = False
141         self.klingons = 0
142         self.romulans = 0
143         self.supernova = False
144         self.charted = False
145         self.status = "secure"  # Could be "secure", "distressed", "enslaved"
146
147 class Page:
148     def __init__(self):
149         self.stars = None
150         self.starbase = None
151         self.klingons = None
152
153 def fill2d(size, fillfun):
154     "Fill an empty list in 2D."
155     lst = []
156     for i in range(size):
157         lst.append([]) 
158         for j in range(size):
159             lst[i].append(fillfun(i, j))
160     return lst
161
162 class Snapshot:
163     def __init__(self):
164         self.snap = False       # snapshot taken
165         self.crew = 0           # crew complement
166         self.remkl = 0          # remaining klingons
167         self.nscrem = 0         # remaining super commanders
168         self.starkl = 0         # destroyed stars
169         self.basekl = 0         # destroyed bases
170         self.nromrem = 0        # Romulans remaining
171         self.nplankl = 0        # destroyed uninhabited planets
172         self.nworldkl = 0       # destroyed inhabited planets
173         self.planets = []       # Planet information
174         self.date = 0.0         # stardate
175         self.remres = 0         # remaining resources
176         self.remtime = 0        # remaining time
177         self.baseq = []         # Base quadrant coordinates
178         self.kcmdr = []         # Commander quadrant coordinates
179         self.kscmdr = Coord()   # Supercommander quadrant coordinates
180         # the galaxy
181         self.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
182         # the starchart
183         self.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
184
185 class Event:
186     def __init__(self):
187         self.date = None        # A real number
188         self.quadrant = None    # A coord structure
189
190 # game options 
191 OPTION_ALL      = 0xffffffff
192 OPTION_TTY      = 0x00000001    # old interface 
193 OPTION_CURSES   = 0x00000002    # new interface 
194 OPTION_IOMODES  = 0x00000003    # cover both interfaces 
195 OPTION_PLANETS  = 0x00000004    # planets and mining 
196 OPTION_THOLIAN  = 0x00000008    # Tholians and their webs (UT 1979 version)
197 OPTION_THINGY   = 0x00000010    # Space Thingy can shoot back (Stas, 2005)
198 OPTION_PROBE    = 0x00000020    # deep-space probes (DECUS version, 1980)
199 OPTION_SHOWME   = 0x00000040    # bracket Enterprise in chart 
200 OPTION_RAMMING  = 0x00000080    # enemies may ram Enterprise (Almy)
201 OPTION_MVBADDY  = 0x00000100    # more enemies can move (Almy)
202 OPTION_BLKHOLE  = 0x00000200    # black hole may timewarp you (Stas, 2005) 
203 OPTION_BASE     = 0x00000400    # bases have good shields (Stas, 2005)
204 OPTION_WORLDS   = 0x00000800    # logic for inhabited worlds (ESR, 2006)
205 OPTION_AUTOSCAN = 0x00001000    # automatic LRSCAN before CHART (ESR, 2006)
206 OPTION_PLAIN    = 0x01000000    # user chose plain game 
207 OPTION_ALMY     = 0x02000000    # user chose Almy variant 
208 OPTION_COLOR    = 0x04000000    # enable color display (experimental, ESR, 2010)
209
210 # Define devices 
211 DSRSENS = 0
212 DLRSENS = 1
213 DPHASER = 2
214 DPHOTON = 3
215 DLIFSUP = 4
216 DWARPEN = 5
217 DIMPULS = 6
218 DSHIELD = 7
219 DRADIO  = 0
220 DSHUTTL = 9
221 DCOMPTR = 10
222 DNAVSYS = 11
223 DTRANSP = 12
224 DSHCTRL = 13
225 DDRAY   = 14
226 DDSP    = 15
227 NDEVICES= 16    # Number of devices
228
229 SKILL_NONE      = 0
230 SKILL_NOVICE    = 1
231 SKILL_FAIR      = 2
232 SKILL_GOOD      = 3
233 SKILL_EXPERT    = 4
234 SKILL_EMERITUS  = 5
235
236 def damaged(dev):       return (game.damage[dev] != 0.0)
237 def communicating():    return not damaged(DRADIO) or game.condition=="docked"
238
239 # Define future events 
240 FSPY    = 0     # Spy event happens always (no future[] entry)
241                 # can cause SC to tractor beam Enterprise
242 FSNOVA  = 1     # Supernova
243 FTBEAM  = 2     # Commander tractor beams Enterprise
244 FSNAP   = 3     # Snapshot for time warp
245 FBATTAK = 4     # Commander attacks base
246 FCDBAS  = 5     # Commander destroys base
247 FSCMOVE = 6     # Supercommander moves (might attack base)
248 FSCDBAS = 7     # Supercommander destroys base
249 FDSPROB = 8     # Move deep space probe
250 FDISTR  = 9     # Emit distress call from an inhabited world 
251 FENSLV  = 10    # Inhabited word is enslaved */
252 FREPRO  = 11    # Klingons build a ship in an enslaved system
253 NEVENTS = 12
254
255 # Abstract out the event handling -- underlying data structures will change
256 # when we implement stateful events 
257 def findevent(evtype):  return game.future[evtype]
258
259 class Enemy:
260     def __init__(self, type=None, loc=None, power=None):
261         self.type = type
262         self.location = Coord()
263         if loc:
264             self.move(loc)
265         self.power = power      # enemy energy level
266         game.enemies.append(self)
267     def move(self, loc):
268         motion = (loc != self.location)
269         if self.location.i is not None and self.location.j is not None:
270             if motion:
271                 if self.type == 'T':
272                     game.quad[self.location.i][self.location.j] = '#'
273                 else:
274                     game.quad[self.location.i][self.location.j] = '.'
275         if loc:
276             self.location = copy.copy(loc)
277             game.quad[self.location.i][self.location.j] = self.type
278             self.kdist = self.kavgd = (game.sector - loc).distance()
279         else:
280             self.location = Coord()
281             self.kdist = self.kavgd = None
282             game.enemies.remove(self)
283         return motion
284     def __repr__(self):
285         return "<%s,%s.%f>" % (self.type, self.location, self.power)    # For debugging
286
287 class Gamestate:
288     def __init__(self):
289         self.options = None     # Game options
290         self.state = Snapshot() # A snapshot structure
291         self.snapsht = Snapshot()       # Last snapshot taken for time-travel purposes
292         self.quad = None        # contents of our quadrant
293         self.damage = [0.0] * NDEVICES  # damage encountered
294         self.future = []        # future events
295         i = NEVENTS
296         while i > 0:
297             i -= 1
298             self.future.append(Event())
299         self.passwd  = None;            # Self Destruct password
300         self.enemies = []
301         self.quadrant = None    # where we are in the large
302         self.sector = None      # where we are in the small
303         self.tholian = None     # Tholian enemy object
304         self.base = None        # position of base in current quadrant
305         self.battle = None      # base coordinates being attacked
306         self.plnet = None       # location of planet in quadrant
307         self.gamewon = False    # Finished!
308         self.ididit = False     # action taken -- allows enemy to attack
309         self.alive = False      # we are alive (not killed)
310         self.justin = False     # just entered quadrant
311         self.shldup = False     # shields are up
312         self.shldchg = False    # shield is changing (affects efficiency)
313         self.iscate = False     # super commander is here
314         self.ientesc = False    # attempted escape from supercommander
315         self.resting = False    # rest time
316         self.icraft = False     # Kirk in Galileo
317         self.landed = False     # party on planet (true), on ship (false)
318         self.alldone = False    # game is now finished
319         self.neutz = False      # Romulan Neutral Zone
320         self.isarmed = False    # probe is armed
321         self.inorbit = False    # orbiting a planet
322         self.imine = False      # mining
323         self.icrystl = False    # dilithium crystals aboard
324         self.iseenit = False    # seen base attack report
325         self.thawed = False     # thawed game
326         self.condition = None   # "green", "yellow", "red", "docked", "dead"
327         self.iscraft = None     # "onship", "offship", "removed"
328         self.skill = None       # Player skill level
329         self.inkling = 0        # initial number of klingons
330         self.inbase = 0         # initial number of bases
331         self.incom = 0          # initial number of commanders
332         self.inscom = 0         # initial number of commanders
333         self.inrom = 0          # initial number of commanders
334         self.instar = 0         # initial stars
335         self.intorps = 0        # initial/max torpedoes
336         self.torps = 0          # number of torpedoes
337         self.ship = 0           # ship type -- 'E' is Enterprise
338         self.abandoned = 0      # count of crew abandoned in space
339         self.length = 0         # length of game
340         self.klhere = 0         # klingons here
341         self.casual = 0         # causalties
342         self.nhelp = 0          # calls for help
343         self.nkinks = 0         # count of energy-barrier crossings
344         self.iplnet = None      # planet # in quadrant
345         self.inplan = 0         # initial planets
346         self.irhere = 0         # Romulans in quadrant
347         self.isatb = 0          # =2 if super commander is attacking base
348         self.tourn = None       # tournament number
349         self.nprobes = 0        # number of probes available
350         self.inresor = 0.0      # initial resources
351         self.intime = 0.0       # initial time
352         self.inenrg = 0.0       # initial/max energy
353         self.inshld = 0.0       # initial/max shield
354         self.inlsr = 0.0        # initial life support resources
355         self.indate = 0.0       # initial date
356         self.energy = 0.0       # energy level
357         self.shield = 0.0       # shield level
358         self.warpfac = 0.0      # warp speed
359         self.lsupres = 0.0      # life support reserves
360         self.optime = 0.0       # time taken by current operation
361         self.damfac = 0.0       # damage factor
362         self.lastchart = 0.0    # time star chart was last updated
363         self.cryprob = 0.0      # probability that crystal will work
364         self.probe = None       # object holding probe course info
365         self.height = 0.0       # height of orbit around planet
366         self.idebug = False     # Debugging instrumentation enabled?
367     def recompute(self):
368         # Stas thinks this should be (C expression): 
369         # game.state.remkl + len(game.state.kcmdr) > 0 ?
370         #       game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr)) : 99
371         # He says the existing expression is prone to divide-by-zero errors
372         # after killing the last klingon when score is shown -- perhaps also
373         # if the only remaining klingon is SCOM.
374         game.state.remtime = game.state.remres/(game.state.remkl + 4*len(game.state.kcmdr))
375
376 FWON = 0
377 FDEPLETE = 1
378 FLIFESUP = 2
379 FNRG = 3
380 FBATTLE = 4
381 FNEG3 = 5
382 FNOVA = 6
383 FSNOVAED = 7
384 FABANDN = 8
385 FDILITHIUM = 9
386 FMATERIALIZE = 10
387 FPHASER = 11
388 FLOST = 12
389 FMINING = 13
390 FDPLANET = 14
391 FPNOVA = 15
392 FSSC = 16
393 FSTRACTOR = 17
394 FDRAY = 18
395 FTRIBBLE = 19
396 FHOLE = 20
397 FCREW = 21
398
399 def withprob(p):
400     return random.random() < p
401
402 def randrange(*args):
403     return random.randrange(*args)
404
405 def randreal(*args):
406     v = random.random()
407     if len(args) == 1:
408         v *= args[0]            # from [0, args[0])
409     elif len(args) == 2:
410         v = args[0] + v*(args[1]-args[0])       # from [args[0], args[1])
411     return v
412
413 # Code from ai.c begins here
414
415 def welcoming(iq):
416     "Would this quadrant welcome another Klingon?"
417     return iq.valid_Quadrant() and \
418         not game.state.galaxy[iq.i][iq.j].supernova and \
419         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
420
421 def tryexit(enemy, look, irun):
422     "A bad guy attempts to bug out."
423     iq = Coord()
424     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))/QUADSIZE - 1
425     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))/QUADSIZE - 1
426     if not welcoming(iq):
427         return False;
428     if enemy.type == 'R':
429         return False; # Romulans cannot escape! 
430     if not irun:
431         # avoid intruding on another commander's territory 
432         if enemy.type == 'C':
433             if iq in game.state.kcmdr:
434                 return False
435             # refuse to leave if currently attacking starbase 
436             if game.battle == game.quadrant:
437                 return False
438         # don't leave if over 1000 units of energy 
439         if enemy.power > 1000.0:
440             return False
441     # emit escape message and move out of quadrant.
442     # we know this if either short or long range sensors are working
443     if not damaged(DSRSENS) or not damaged(DLRSENS) or \
444         game.condition == "docked":
445         prout(crmena(True, enemy.type, "sector", enemy.location) + \
446               (_(" escapes to Quadrant %s (and regains strength).") % iq))
447     # handle local matters related to escape
448     enemy.move(None)
449     game.klhere -= 1
450     if game.condition != "docked":
451         newcnd()
452     # Handle global matters related to escape 
453     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
454     game.state.galaxy[iq.i][iq.j].klingons += 1
455     if enemy.type=='S':
456         game.iscate = False
457         game.ientesc = False
458         game.isatb = 0
459         schedule(FSCMOVE, 0.2777)
460         unschedule(FSCDBAS)
461         game.state.kscmdr=iq
462     else:
463         for cmdr in game.state.kcmdr:
464             if cmdr == game.quadrant:
465                 game.state.kcmdr.append(iq)
466                 break
467     return True; # success 
468
469 # The bad-guy movement algorithm:
470
471 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
472 # If both are operating full strength, force is 1000. If both are damaged,
473 # force is -1000. Having shields down subtracts an additional 1000.
474
475 # 2. Enemy has forces equal to the energy of the attacker plus
476 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
477 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
478
479 # Attacker Initial energy levels (nominal):
480 # Klingon   Romulan   Commander   Super-Commander
481 # Novice    400        700        1200        
482 # Fair      425        750        1250
483 # Good      450        800        1300        1750
484 # Expert    475        850        1350        1875
485 # Emeritus  500        900        1400        2000
486 # VARIANCE   75        200         200         200
487
488 # Enemy vessels only move prior to their attack. In Novice - Good games
489 # only commanders move. In Expert games, all enemy vessels move if there
490 # is a commander present. In Emeritus games all enemy vessels move.
491
492 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
493 # forces are 1000 greater than Enterprise.
494
495 # Agressive action on average cuts the distance between the ship and
496 # the enemy to 1/4 the original.
497
498 # 4.  At lower energy advantage, movement units are proportional to the
499 # advantage with a 650 advantage being to hold ground, 800 to move forward
500 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
501
502 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
503 # retreat, especially at high skill levels.
504
505 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
506
507 def movebaddy(enemy):
508     "Tactical movement for the bad guys."
509     goto = Coord(); look = Coord()
510     irun = False
511     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant) 
512     if game.skill >= SKILL_EXPERT:
513         nbaddys = (((game.quadrant in game.state.kcmdr)*2 + (game.state.kscmdr==game.quadrant)*2+game.klhere*1.23+game.irhere*1.5)/2.0)
514     else:
515         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
516     dist1 = enemy.kdist
517     mdist = int(dist1 + 0.5); # Nearest integer distance 
518     # If SC, check with spy to see if should hi-tail it 
519     if enemy.type=='S' and \
520         (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
521         irun = True
522         motion = -QUADSIZE
523     else:
524         # decide whether to advance, retreat, or hold position 
525         forces = enemy.power+100.0*len(game.enemies)+400*(nbaddys-1)
526         if not game.shldup:
527             forces += 1000; # Good for enemy if shield is down! 
528         if not damaged(DPHASER) or not damaged(DPHOTON):
529             if damaged(DPHASER): # phasers damaged 
530                 forces += 300.0
531             else:
532                 forces -= 0.2*(game.energy - 2500.0)
533             if damaged(DPHOTON): # photon torpedoes damaged 
534                 forces += 300.0
535             else:
536                 forces -= 50.0*game.torps
537         else:
538             # phasers and photon tubes both out! 
539             forces += 1000.0
540         motion = 0
541         if forces <= 1000.0 and game.condition != "docked": # Typical situation 
542             motion = ((forces + randreal(200))/150.0) - 5.0
543         else:
544             if forces > 1000.0: # Very strong -- move in for kill 
545                 motion = (1.0 - randreal())**2 * dist1 + 1.0
546             if game.condition=="docked" and (game.options & OPTION_BASE): # protected by base -- back off ! 
547                 motion -= game.skill*(2.0-randreal()**2)
548         if game.idebug:
549             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
550         # don't move if no motion 
551         if motion==0:
552             return
553         # Limit motion according to skill 
554         if abs(motion) > game.skill:
555             if motion < 0:
556                 motion = -game.skill
557             else:
558                 motion = game.skill
559     # calculate preferred number of steps 
560     nsteps = abs(int(motion))
561     if motion > 0 and nsteps > mdist:
562         nsteps = mdist; # don't overshoot 
563     if nsteps > QUADSIZE:
564         nsteps = QUADSIZE; # This shouldn't be necessary 
565     if nsteps < 1:
566         nsteps = 1; # This shouldn't be necessary 
567     if game.idebug:
568         proutn("NSTEPS = %d:" % nsteps)
569     # Compute preferred values of delta X and Y 
570     m = game.sector - enemy.location
571     if 2.0 * abs(m.i) < abs(m.j):
572         m.i = 0
573     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
574         m.j = 0
575     m = (motion * m).sgn()
576     goto = enemy.location
577     # main move loop 
578     for ll in range(nsteps):
579         if game.idebug:
580             proutn(" %d" % (ll+1))
581         # Check if preferred position available 
582         look = goto + m
583         if m.i < 0:
584             krawli = 1
585         else:
586             krawli = -1
587         if m.j < 0:
588             krawlj = 1
589         else:
590             krawlj = -1
591         success = False
592         attempts = 0; # Settle mysterious hang problem 
593         while attempts < 20 and not success:
594             attempts += 1
595             if look.i < 0 or look.i >= QUADSIZE:
596                 if motion < 0 and tryexit(enemy, look, irun):
597                     return
598                 if krawli == m.i or m.j == 0:
599                     break
600                 look.i = goto.i + krawli
601                 krawli = -krawli
602             elif look.j < 0 or look.j >= QUADSIZE:
603                 if motion < 0 and tryexit(enemy, look, irun):
604                     return
605                 if krawlj == m.j or m.i == 0:
606                     break
607                 look.j = goto.j + krawlj
608                 krawlj = -krawlj
609             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
610                 # See if enemy should ram ship 
611                 if game.quad[look.i][look.j] == game.ship and \
612                     (enemy.type == 'C' or enemy.type == 'S'):
613                     collision(rammed=True, enemy=enemy)
614                     return
615                 if krawli != m.i and m.j != 0:
616                     look.i = goto.i + krawli
617                     krawli = -krawli
618                 elif krawlj != m.j and m.i != 0:
619                     look.j = goto.j + krawlj
620                     krawlj = -krawlj
621                 else:
622                     break; # we have failed 
623             else:
624                 success = True
625         if success:
626             goto = look
627             if game.idebug:
628                 proutn(`goto`)
629         else:
630             break; # done early 
631     if game.idebug:
632         skip(1)
633     if enemy.move(goto):
634         if not damaged(DSRSENS) or game.condition == "docked":
635             proutn(_("*** %s from Sector %s") % (cramen(enemy.type), enemy.location))
636             if enemy.kdist < dist1:
637                 proutn(_(" advances to "))
638             else:
639                 proutn(_(" retreats to "))
640             prout("Sector %s." % goto)
641
642 def moveklings():
643     "Sequence Klingon tactical movement."
644     if game.idebug:
645         prout("== MOVCOM")
646     # Figure out which Klingon is the commander (or Supercommander)
647     # and do move
648     if game.quadrant in game.state.kcmdr:
649         for enemy in game.enemies:
650             if enemy.type == 'C':
651                 movebaddy(enemy)
652     if game.state.kscmdr==game.quadrant:
653         for enemy in game.enemies:
654             if enemy.type == 'S':
655                 movebaddy(enemy)
656                 break
657     # If skill level is high, move other Klingons and Romulans too!
658     # Move these last so they can base their actions on what the
659     # commander(s) do.
660     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
661         for enemy in game.enemies:
662             if enemy.type in ('K', 'R'):
663                 movebaddy(enemy)
664     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
665
666 def movescom(iq, avoid):
667     "Commander movement helper." 
668     # Avoid quadrants with bases if we want to avoid Enterprise 
669     if not welcoming(iq) or (avoid and iq in game.state.baseq):
670         return False
671     if game.justin and not game.iscate:
672         return False
673     # do the move 
674     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
675     game.state.kscmdr = iq
676     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
677     if game.state.kscmdr==game.quadrant:
678         # SC has scooted, remove him from current quadrant 
679         game.iscate=False
680         game.isatb=0
681         game.ientesc = False
682         unschedule(FSCDBAS)
683         for enemy in game.enemies:
684             if enemy.type == 'S':
685                 break
686         enemy.move(None)
687         game.klhere -= 1
688         if game.condition != "docked":
689             newcnd()
690         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
691     # check for a helpful planet 
692     for i in range(game.inplan):
693         if game.state.planets[i].quadrant == game.state.kscmdr and \
694             game.state.planets[i].crystals == "present":
695             # destroy the planet 
696             game.state.planets[i].pclass = "destroyed"
697             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
698             if communicating():
699                 announce()
700                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
701                 proutn(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
702                 prout(_("   by the Super-commander.\""))
703             break
704     return True; # looks good! 
705                         
706 def supercommander():
707     "Move the Super Commander." 
708     iq = Coord(); sc = Coord(); ibq = Coord(); idelta = Coord()
709     basetbl = []
710     if game.idebug:
711         prout("== SUPERCOMMANDER")
712     # Decide on being active or passive 
713     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 \
714             (game.state.date-game.indate) < 3.0)
715     if not game.iscate and avoid:
716         # compute move away from Enterprise 
717         idelta = game.state.kscmdr-game.quadrant
718         if idelta.distance() > 2.0:
719             # circulate in space 
720             idelta.i = game.state.kscmdr.j-game.quadrant.j
721             idelta.j = game.quadrant.i-game.state.kscmdr.i
722     else:
723         # compute distances to starbases 
724         if not game.state.baseq:
725             # nothing left to do 
726             unschedule(FSCMOVE)
727             return
728         sc = game.state.kscmdr
729         for base in game.state.baseq:
730             basetbl.append((i, (base - sc).distance()))
731         if game.state.baseq > 1:
732             basetbl.sort(lambda x, y: cmp(x[1], y[1]))
733         # look for nearest base without a commander, no Enterprise, and
734         # without too many Klingons, and not already under attack. 
735         ifindit = iwhichb = 0
736         for (i2, base) in enumerate(game.state.baseq):
737             i = basetbl[i2][0]; # bug in original had it not finding nearest
738             if base==game.quadrant or base==game.battle or not welcoming(base):
739                 continue
740             # if there is a commander, and no other base is appropriate,
741             # we will take the one with the commander
742             for cmdr in game.state.kcmdr:
743                 if base == cmdr and ifindit != 2:
744                     ifindit = 2
745                     iwhichb = i
746                     break
747             else:       # no commander -- use this one 
748                 ifindit = 1
749                 iwhichb = i
750                 break
751         if ifindit==0:
752             return # Nothing suitable -- wait until next time
753         ibq = game.state.baseq[iwhichb]
754         # decide how to move toward base 
755         idelta = ibq - game.state.kscmdr
756     # Maximum movement is 1 quadrant in either or both axes 
757     idelta = idelta.sgn()
758     # try moving in both x and y directions
759     # there was what looked like a bug in the Almy C code here,
760     # but it might be this translation is just wrong.
761     iq = game.state.kscmdr + idelta
762     if not movescom(iq, avoid):
763         # failed -- try some other maneuvers 
764         if idelta.i==0 or idelta.j==0:
765             # attempt angle move 
766             if idelta.i != 0:
767                 iq.j = game.state.kscmdr.j + 1
768                 if not movescom(iq, avoid):
769                     iq.j = game.state.kscmdr.j - 1
770                     movescom(iq, avoid)
771             elif idelta.j != 0:
772                 iq.i = game.state.kscmdr.i + 1
773                 if not movescom(iq, avoid):
774                     iq.i = game.state.kscmdr.i - 1
775                     movescom(iq, avoid)
776         else:
777             # try moving just in x or y 
778             iq.j = game.state.kscmdr.j
779             if not movescom(iq, avoid):
780                 iq.j = game.state.kscmdr.j + idelta.j
781                 iq.i = game.state.kscmdr.i
782                 movescom(iq, avoid)
783     # check for a base 
784     if len(game.state.baseq) == 0:
785         unschedule(FSCMOVE)
786     else:
787         for ibq in game.state.baseq:
788             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
789                 # attack the base 
790                 if avoid:
791                     return # no, don't attack base! 
792                 game.iseenit = False
793                 game.isatb = 1
794                 schedule(FSCDBAS, randreal(1.0, 3.0))
795                 if is_scheduled(FCDBAS):
796                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
797                 if not communicating():
798                     return # no warning 
799                 game.iseenit = True
800                 announce()
801                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
802                       % game.state.kscmdr)
803                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
804                 proutn(_("   It can survive until stardate %d.\"") \
805                        % int(scheduled(FSCDBAS)))
806                 if not game.resting:
807                     return
808                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
809                 if not ja():
810                     return
811                 game.resting = False
812                 game.optime = 0.0; # actually finished 
813                 return
814     # Check for intelligence report 
815     if not game.idebug and \
816         (withprob(0.8) or \
817          (not communicating()) or \
818          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
819         return
820     announce()
821     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
822     proutn(_("   the Super-commander is in Quadrant %s,") % game.state.kscmdr)
823     return
824
825 def movetholian():
826     "Move the Tholian."
827     if not game.tholian or game.justin:
828         return
829     tid = Coord()
830     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
831         tid.i = 0; tid.j = QUADSIZE-1
832     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
833         tid.i = QUADSIZE-1; tid.j = QUADSIZE-1
834     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
835         tid.i = QUADSIZE-1; tid.j = 0
836     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
837         tid.i = 0; tid.j = 0
838     else:
839         # something is wrong! 
840         game.tholian.move(None)
841         prout("***Internal error: Tholian in a bad spot.")
842         return
843     # do nothing if we are blocked 
844     if game.quad[tid.i][tid.j] not in ('.', '#'):
845         return
846     here = copy.copy(game.tholian.location)
847     delta = (tid - game.tholian.location).sgn()
848     # move in x axis 
849     while here.i != tid.i:
850         here.i += delta.i
851         if game.quad[here.i][here.j]=='.':
852             game.tholian.move(here)
853     # move in y axis 
854     while here.j != tid.j:
855         here.j += delta.j
856         if game.quad[here.i][here.j]=='.':
857             game.tholian.move(here)
858     # check to see if all holes plugged 
859     for i in range(QUADSIZE):
860         if game.quad[0][i]!='#' and game.quad[0][i]!='T':
861             return
862         if game.quad[QUADSIZE-1][i]!='#' and game.quad[QUADSIZE-1][i]!='T':
863             return
864         if game.quad[i][0]!='#' and game.quad[i][0]!='T':
865             return
866         if game.quad[i][QUADSIZE-1]!='#' and game.quad[i][QUADSIZE-1]!='T':
867             return
868     # All plugged up -- Tholian splits 
869     game.quad[game.tholian.location.i][game.tholian.location.j]='#'
870     dropin(' ')
871     prout(crmena(True, 'T', "sector", game.tholian) + _(" completes web."))
872     game.tholian.move(None)
873     return
874
875 # Code from battle.c begins here
876
877 def doshield(shraise):
878     "Change shield status."
879     action = "NONE"
880     game.ididit = False
881     if shraise:
882         action = "SHUP"
883     else:
884         key = scanner.next()
885         if key == "IHALPHA":
886             if scanner.sees("transfer"):
887                 action = "NRG"
888             else:
889                 if damaged(DSHIELD):
890                     prout(_("Shields damaged and down."))
891                     return
892                 if scanner.sees("up"):
893                     action = "SHUP"
894                 elif scanner.sees("down"):
895                     action = "SHDN"
896         if action=="NONE":
897             proutn(_("Do you wish to change shield energy? "))
898             if ja():
899                 action = "NRG"
900             elif damaged(DSHIELD):
901                 prout(_("Shields damaged and down."))
902                 return
903             elif game.shldup:
904                 proutn(_("Shields are up. Do you want them down? "))
905                 if ja():
906                     action = "SHDN"
907                 else:
908                     scanner.chew()
909                     return
910             else:
911                 proutn(_("Shields are down. Do you want them up? "))
912                 if ja():
913                     action = "SHUP"
914                 else:
915                     scanner.chew()
916                     return    
917     if action == "SHUP": # raise shields 
918         if game.shldup:
919             prout(_("Shields already up."))
920             return
921         game.shldup = True
922         game.shldchg = True
923         if game.condition != "docked":
924             game.energy -= 50.0
925         prout(_("Shields raised."))
926         if game.energy <= 0:
927             skip(1)
928             prout(_("Shields raising uses up last of energy."))
929             finish(FNRG)
930             return
931         game.ididit=True
932         return
933     elif action == "SHDN":
934         if not game.shldup:
935             prout(_("Shields already down."))
936             return
937         game.shldup=False
938         game.shldchg=True
939         prout(_("Shields lowered."))
940         game.ididit = True
941         return
942     elif action == "NRG":
943         while scanner.next() != "IHREAL":
944             scanner.chew()
945             proutn(_("Energy to transfer to shields- "))
946         nrg = scanner.real
947         scanner.chew()
948         if nrg == 0:
949             return
950         if nrg > game.energy:
951             prout(_("Insufficient ship energy."))
952             return
953         game.ididit = True
954         if game.shield+nrg >= game.inshld:
955             prout(_("Shield energy maximized."))
956             if game.shield+nrg > game.inshld:
957                 prout(_("Excess energy requested returned to ship energy"))
958             game.energy -= game.inshld-game.shield
959             game.shield = game.inshld
960             return
961         if nrg < 0.0 and game.energy-nrg > game.inenrg:
962             # Prevent shield drain loophole 
963             skip(1)
964             prout(_("Engineering to bridge--"))
965             prout(_("  Scott here. Power circuit problem, Captain."))
966             prout(_("  I can't drain the shields."))
967             game.ididit = False
968             return
969         if game.shield+nrg < 0:
970             prout(_("All shield energy transferred to ship."))
971             game.energy += game.shield
972             game.shield = 0.0
973             return
974         proutn(_("Scotty- \""))
975         if nrg > 0:
976             prout(_("Transferring energy to shields.\""))
977         else:
978             prout(_("Draining energy from shields.\""))
979         game.shield += nrg
980         game.energy -= nrg
981         return
982
983 def randdevice():
984     "Choose a device to damage, at random."
985     weights = (
986         105,    # DSRSENS: short range scanners 10.5% 
987         105,    # DLRSENS: long range scanners          10.5% 
988         120,    # DPHASER: phasers                      12.0% 
989         120,    # DPHOTON: photon torpedoes             12.0% 
990         25,     # DLIFSUP: life support                  2.5% 
991         65,     # DWARPEN: warp drive                    6.5% 
992         70,     # DIMPULS: impulse engines               6.5% 
993         145,    # DSHIELD: deflector shields            14.5% 
994         30,     # DRADIO:  subspace radio                3.0% 
995         45,     # DSHUTTL: shuttle                       4.5% 
996         15,     # DCOMPTR: computer                      1.5% 
997         20,     # NAVCOMP: navigation system             2.0% 
998         75,     # DTRANSP: transporter                   7.5% 
999         20,     # DSHCTRL: high-speed shield controller  2.0% 
1000         10,     # DDRAY: death ray                       1.0% 
1001         30,     # DDSP: deep-space probes                3.0% 
1002     )
1003     assert(sum(weights) == 1000)
1004     idx = randrange(1000)
1005     wsum = 0
1006     for (i, w) in enumerate(weights):
1007         wsum += w
1008         if idx < wsum:
1009             return i
1010     return None;        # we should never get here
1011
1012 def collision(rammed, enemy):
1013     "Collision handling fot rammong events."
1014     prouts(_("***RED ALERT!  RED ALERT!"))
1015     skip(1)
1016     prout(_("***COLLISION IMMINENT."))
1017     skip(2)
1018     proutn("***")
1019     proutn(crmshp())
1020     hardness = {'R':1.5, 'C':2.0, 'S':2.5, 'T':0.5, '?':4.0}.get(enemy.type, 1.0)
1021     if rammed:
1022         proutn(_(" rammed by "))
1023     else:
1024         proutn(_(" rams "))
1025     proutn(crmena(False, enemy.type, "sector", enemy.location))
1026     if rammed:
1027         proutn(_(" (original position)"))
1028     skip(1)
1029     deadkl(enemy.location, enemy.type, game.sector)
1030     proutn("***" + crmshp() + " heavily damaged.")
1031     icas = randrange(10, 30)
1032     prout(_("***Sickbay reports %d casualties") % icas)
1033     game.casual += icas
1034     game.state.crew -= icas
1035     # In the pre-SST2K version, all devices got equiprobably damaged,
1036     # which was silly.  Instead, pick up to half the devices at
1037     # random according to our weighting table,
1038     ncrits = randrange(NDEVICES/2)
1039     while ncrits > 0:
1040         ncrits -= 1
1041         dev = randdevice()
1042         if game.damage[dev] < 0:
1043             continue
1044         extradm = (10.0*hardness*randreal()+1.0)*game.damfac
1045         # Damage for at least time of travel! 
1046         game.damage[dev] += game.optime + extradm
1047     game.shldup = False
1048     prout(_("***Shields are down."))
1049     if game.state.remkl + len(game.state.kcmdr) + game.state.nscrem:
1050         announce()
1051         damagereport()
1052     else:
1053         finish(FWON)
1054     return
1055
1056 def torpedo(origin, bearing, dispersion, number, nburst):
1057     "Let a photon torpedo fly" 
1058     if not damaged(DSRSENS) or game.condition=="docked":
1059         setwnd(srscan_window)
1060     else: 
1061         setwnd(message_window)
1062     ac = bearing + 0.25*dispersion      # dispersion is a random variable
1063     bullseye = (15.0 - bearing)*0.5235988
1064     track = course(bearing=ac, distance=QUADSIZE, origin=cartesian(origin)) 
1065     bumpto = Coord(0, 0)
1066     # Loop to move a single torpedo 
1067     setwnd(message_window)
1068     for step in range(1, QUADSIZE*2):
1069         if not track.next(): break
1070         w = track.sector()
1071         if not w.valid_sector():
1072             break
1073         iquad=game.quad[w.i][w.j]
1074         tracktorpedo(origin, w, step, number, nburst, iquad)
1075         if iquad=='.':
1076             continue
1077         # hit something 
1078         if not damaged(DSRSENS) or game.condition == "docked":
1079             skip(1);    # start new line after text track 
1080         if iquad in ('E', 'F'): # Hit our ship 
1081             skip(1)
1082             prout(_("Torpedo hits %s.") % crmshp())
1083             hit = 700.0 + randreal(100) - \
1084                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1085             newcnd(); # we're blown out of dock 
1086             if game.landed or game.condition=="docked":
1087                 return hit # Cheat if on a planet 
1088             # In the C/FORTRAN version, dispersion was 2.5 radians, which
1089             # is 143 degrees, which is almost exactly 4.8 clockface units
1090             displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
1091             displacement.next()
1092             bumpto = displacement.sector()
1093             if not bumpto.valid_sector():
1094                 return hit
1095             if game.quad[bumpto.i][bumpto.j]==' ':
1096                 finish(FHOLE)
1097                 return hit
1098             if game.quad[bumpto.i][bumpto.j]!='.':
1099                 # can't move into object 
1100                 return hit
1101             game.sector = bumpto
1102             proutn(crmshp())
1103             game.quad[w.i][w.j]='.'
1104             game.quad[bumpto.i][bumpto.j]=iquad
1105             prout(_(" displaced by blast to Sector %s ") % bumpto)
1106             for enemy in game.enemies:
1107                 enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1108             game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1109             return None
1110         elif iquad in ('C', 'S', 'R', 'K'): # Hit a regular enemy 
1111             # find the enemy 
1112             if iquad in ('C', 'S') and withprob(0.05):
1113                 prout(crmena(True, iquad, "sector", w) + _(" uses anti-photon device;"))
1114                 prout(_("   torpedo neutralized."))
1115                 return None
1116             for enemy in game.enemies:
1117                 if w == enemy.location:
1118                     break
1119             kp = math.fabs(enemy.power)
1120             h1 = 700.0 + randrange(100) - \
1121                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-track.angle))
1122             h1 = math.fabs(h1)
1123             if kp < h1:
1124                 h1 = kp
1125             if enemy.power < 0:
1126                 enemy.power -= -h1
1127             else:
1128                 enemy.power -= h1
1129             if enemy.power == 0:
1130                 deadkl(w, iquad, w)
1131                 return None
1132             proutn(crmena(True, iquad, "sector", w))
1133             displacement = course(track.bearing+randreal(-2.4,2.4), distance=2**0.5)
1134             displacement.next()
1135             bumpto = displacement.sector()
1136             if not bumpto.valid_sector():
1137                 prout(_(" damaged but not destroyed."))
1138                 return
1139             if game.quad[bumpto.i][bumpto.j] == ' ':
1140                 prout(_(" buffeted into black hole."))
1141                 deadkl(w, iquad, bumpto)
1142             if game.quad[bumpto.i][bumpto.j] != '.':
1143                 prout(_(" damaged but not destroyed."))
1144             else:
1145                 prout(_(" damaged-- displaced by blast to Sector %s ")%bumpto)
1146                 enemy.location = bumpto
1147                 game.quad[w.i][w.j]='.'
1148                 game.quad[bumpto.i][bumpto.j]=iquad
1149                 for enemy in game.enemies:
1150                     enemy.kdist = enemy.kavgd = (game.sector-enemy.location).distance()
1151                 game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1152             return None
1153         elif iquad == 'B': # Hit a base 
1154             skip(1)
1155             prout(_("***STARBASE DESTROYED.."))
1156             game.state.baseq = filter(lambda x: x != game.quadrant, game.state.baseq)
1157             game.quad[w.i][w.j]='.'
1158             game.base.invalidate()
1159             game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase -= 1
1160             game.state.chart[game.quadrant.i][game.quadrant.j].starbase -= 1
1161             game.state.basekl += 1
1162             newcnd()
1163             return None
1164         elif iquad == 'P': # Hit a planet 
1165             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1166             game.state.nplankl += 1
1167             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1168             game.iplnet.pclass = "destroyed"
1169             game.iplnet = None
1170             game.plnet.invalidate()
1171             game.quad[w.i][w.j] = '.'
1172             if game.landed:
1173                 # captain perishes on planet 
1174                 finish(FDPLANET)
1175             return None
1176         elif iquad == '@': # Hit an inhabited world -- very bad! 
1177             prout(crmena(True, iquad, "sector", w) + _(" destroyed."))
1178             game.state.nworldkl += 1
1179             game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
1180             game.iplnet.pclass = "destroyed"
1181             game.iplnet = None
1182             game.plnet.invalidate()
1183             game.quad[w.i][w.j] = '.'
1184             if game.landed:
1185                 # captain perishes on planet 
1186                 finish(FDPLANET)
1187             prout(_("The torpedo destroyed an inhabited planet."))
1188             return None
1189         elif iquad == '*': # Hit a star 
1190             if withprob(0.9):
1191                 nova(w)
1192             else:
1193                 prout(crmena(True, '*', "sector", w) + _(" unaffected by photon blast."))
1194             return None
1195         elif iquad == '?': # Hit a thingy 
1196             if not (game.options & OPTION_THINGY) or withprob(0.3):
1197                 skip(1)
1198                 prouts(_("AAAAIIIIEEEEEEEEAAAAAAAAUUUUUGGGGGHHHHHHHHHHHH!!!"))
1199                 skip(1)
1200                 prouts(_("    HACK!     HACK!    HACK!        *CHOKE!*  "))
1201                 skip(1)
1202                 proutn(_("Mr. Spock-"))
1203                 prouts(_("  \"Fascinating!\""))
1204                 skip(1)
1205                 deadkl(w, iquad, w)
1206             else:
1207                 # Stas Sergeev added the possibility that
1208                 # you can shove the Thingy and piss it off.
1209                 # It then becomes an enemy and may fire at you.
1210                 thing.angry = True
1211             return None
1212         elif iquad == ' ': # Black hole 
1213             skip(1)
1214             prout(crmena(True, ' ', "sector", w) + _(" swallows torpedo."))
1215             return None
1216         elif iquad == '#': # hit the web 
1217             skip(1)
1218             prout(_("***Torpedo absorbed by Tholian web."))
1219             return None
1220         elif iquad == 'T':  # Hit a Tholian 
1221             h1 = 700.0 + randrange(100) - \
1222                 1000.0 * (w-origin).distance() * math.fabs(math.sin(bullseye-angle))
1223             h1 = math.fabs(h1)
1224             if h1 >= 600:
1225                 game.quad[w.i][w.j] = '.'
1226                 deadkl(w, iquad, w)
1227                 game.tholian = None
1228                 return None
1229             skip(1)
1230             proutn(crmena(True, 'T', "sector", w))
1231             if withprob(0.05):
1232                 prout(_(" survives photon blast."))
1233                 return None
1234             prout(_(" disappears."))
1235             game.tholian.move(None)
1236             game.quad[w.i][w.j] = '#'
1237             dropin(' ')
1238             return None
1239         else: # Problem!
1240             skip(1)
1241             proutn("Don't know how to handle torpedo collision with ")
1242             proutn(crmena(True, iquad, "sector", w))
1243             skip(1)
1244             return None
1245         break
1246     skip(1)
1247     prout(_("Torpedo missed."))
1248     return None;
1249
1250 def fry(hit):
1251     "Critical-hit resolution." 
1252     if hit < (275.0-25.0*game.skill)*randreal(1.0, 1.5):
1253         return
1254     ncrit = int(1.0 + hit/(500.0+randreal(100)))
1255     proutn(_("***CRITICAL HIT--"))
1256     # Select devices and cause damage
1257     cdam = []
1258     while ncrit > 0:
1259         ncrit -= 1
1260         while True:
1261             j = randdevice()
1262             # Cheat to prevent shuttle damage unless on ship 
1263             if not (game.damage[j]<0.0 or (j==DSHUTTL and game.iscraft != "onship")):
1264                 break
1265         cdam.append(j)
1266         extradm = (hit*game.damfac)/(ncrit*randreal(75, 100))
1267         game.damage[j] += extradm
1268     skipcount = 0
1269     for (i, j) in enumerate(cdam):
1270         proutn(device[j])
1271         if skipcount % 3 == 2 and i < len(cdam)-1:
1272             skip(1)
1273         skipcount += 1
1274         if i < len(cdam)-1:
1275             proutn(_(" and "))
1276     prout(_(" damaged."))
1277     if damaged(DSHIELD) and game.shldup:
1278         prout(_("***Shields knocked down."))
1279         game.shldup=False
1280
1281 def attack(torps_ok):
1282     # bad guy attacks us 
1283     # torps_ok == False forces use of phasers in an attack 
1284     # game could be over at this point, check
1285     if game.alldone:
1286         return
1287     attempt = False; ihurt = False;
1288     hitmax=0.0; hittot=0.0; chgfac=1.0
1289     where = "neither"
1290     if game.idebug:
1291         prout("=== ATTACK!")
1292     # Tholian gets to move before attacking 
1293     if game.tholian:
1294         movetholian()
1295     # if you have just entered the RNZ, you'll get a warning 
1296     if game.neutz: # The one chance not to be attacked 
1297         game.neutz = False
1298         return
1299     # commanders get a chance to tac-move towards you 
1300     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:
1301         moveklings()
1302     # if no enemies remain after movement, we're done 
1303     if len(game.enemies)==0 or (len(game.enemies)==1 and thing == game.quadrant and not thing.angry):
1304         return
1305     # set up partial hits if attack happens during shield status change 
1306     pfac = 1.0/game.inshld
1307     if game.shldchg:
1308         chgfac = 0.25 + randreal(0.5)
1309     skip(1)
1310     # message verbosity control 
1311     if game.skill <= SKILL_FAIR:
1312         where = "sector"
1313     for enemy in game.enemies:
1314         if enemy.power < 0:
1315             continue;   # too weak to attack 
1316         # compute hit strength and diminish shield power 
1317         r = randreal()
1318         # Increase chance of photon torpedos if docked or enemy energy is low 
1319         if game.condition == "docked":
1320             r *= 0.25
1321         if enemy.power < 500:
1322             r *= 0.25; 
1323         if enemy.type=='T' or (enemy.type=='?' and not thing.angry):
1324             continue
1325         # different enemies have different probabilities of throwing a torp 
1326         usephasers = not torps_ok or \
1327             (enemy.type == 'K' and r > 0.0005) or \
1328             (enemy.type=='C' and r > 0.015) or \
1329             (enemy.type=='R' and r > 0.3) or \
1330             (enemy.type=='S' and r > 0.07) or \
1331             (enemy.type=='?' and r > 0.05)
1332         if usephasers:      # Enemy uses phasers 
1333             if game.condition == "docked":
1334                 continue; # Don't waste the effort! 
1335             attempt = True; # Attempt to attack 
1336             dustfac = randreal(0.8, 0.85)
1337             hit = enemy.power*math.pow(dustfac,enemy.kavgd)
1338             enemy.power *= 0.75
1339         else: # Enemy uses photon torpedo 
1340             # We should be able to make the bearing() method work here
1341             pcourse = 1.90985*math.atan2(game.sector.j-enemy.location.j, enemy.location.i-game.sector.i)
1342             hit = 0
1343             proutn(_("***TORPEDO INCOMING"))
1344             if not damaged(DSRSENS):
1345                 proutn(_(" From ") + crmena(False, enemy.type, where, enemy.location))
1346             attempt = True
1347             prout("  ")
1348             dispersion = (randreal()+randreal())*0.5 - 0.5
1349             dispersion += 0.002*enemy.power*dispersion
1350             hit = torpedo(enemy.location, pcourse, dispersion, number=1, nburst=1)
1351             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1352                 finish(FWON); # Klingons did themselves in! 
1353             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.alldone:
1354                 return # Supernova or finished 
1355             if hit == None:
1356                 continue
1357         # incoming phaser or torpedo, shields may dissipate it 
1358         if game.shldup or game.shldchg or game.condition=="docked":
1359             # shields will take hits 
1360             propor = pfac * game.shield
1361             if game.condition =="docked":
1362                 propor *= 2.1
1363             if propor < 0.1:
1364                 propor = 0.1
1365             hitsh = propor*chgfac*hit+1.0
1366             absorb = 0.8*hitsh
1367             if absorb > game.shield:
1368                 absorb = game.shield
1369             game.shield -= absorb
1370             hit -= hitsh
1371             # taking a hit blasts us out of a starbase dock 
1372             if game.condition == "docked":
1373                 dock(False)
1374             # but the shields may take care of it 
1375             if propor > 0.1 and hit < 0.005*game.energy:
1376                 continue
1377         # hit from this opponent got through shields, so take damage 
1378         ihurt = True
1379         proutn(_("%d unit hit") % int(hit))
1380         if (damaged(DSRSENS) and usephasers) or game.skill<=SKILL_FAIR:
1381             proutn(_(" on the ") + crmshp())
1382         if not damaged(DSRSENS) and usephasers:
1383             prout(_(" from ") + crmena(False, enemy.type, where, enemy.location))
1384         skip(1)
1385         # Decide if hit is critical 
1386         if hit > hitmax:
1387             hitmax = hit
1388         hittot += hit
1389         fry(hit)
1390         game.energy -= hit
1391     if game.energy <= 0:
1392         # Returning home upon your shield, not with it... 
1393         finish(FBATTLE)
1394         return
1395     if not attempt and game.condition == "docked":
1396         prout(_("***Enemies decide against attacking your ship."))
1397     percent = 100.0*pfac*game.shield+0.5
1398     if not ihurt:
1399         # Shields fully protect ship 
1400         proutn(_("Enemy attack reduces shield strength to "))
1401     else:
1402         # Emit message if starship suffered hit(s) 
1403         skip(1)
1404         proutn(_("Energy left %2d    shields ") % int(game.energy))
1405         if game.shldup:
1406             proutn(_("up "))
1407         elif not damaged(DSHIELD):
1408             proutn(_("down "))
1409         else:
1410             proutn(_("damaged, "))
1411     prout(_("%d%%,   torpedoes left %d") % (percent, game.torps))
1412     # Check if anyone was hurt 
1413     if hitmax >= 200 or hittot >= 500:
1414         icas = randrange(int(hittot * 0.015))
1415         if icas >= 2:
1416             skip(1)
1417             prout(_("Mc Coy-  \"Sickbay to bridge.  We suffered %d casualties") % icas)
1418             prout(_("   in that last attack.\""))
1419             game.casual += icas
1420             game.state.crew -= icas
1421     # After attack, reset average distance to enemies 
1422     for enemy in game.enemies:
1423         enemy.kavgd = enemy.kdist
1424     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
1425     return
1426                 
1427 def deadkl(w, type, mv):
1428     "Kill a Klingon, Tholian, Romulan, or Thingy." 
1429     # Added mv to allow enemy to "move" before dying 
1430     proutn(crmena(True, type, "sector", mv))
1431     # Decide what kind of enemy it is and update appropriately 
1432     if type == 'R':
1433         # Chalk up a Romulan 
1434         game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans -= 1
1435         game.irhere -= 1
1436         game.state.nromrem -= 1
1437     elif type == 'T':
1438         # Killed a Tholian 
1439         game.tholian = None
1440     elif type == '?':
1441         # Killed a Thingy
1442         global thing
1443         thing = None
1444     else:
1445         # Killed some type of Klingon 
1446         game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
1447         game.klhere -= 1
1448         if type == 'C':
1449             game.state.kcmdr.remove(game.quadrant)
1450             unschedule(FTBEAM)
1451             if game.state.kcmdr:
1452                 schedule(FTBEAM, expran(1.0*game.incom/len(game.state.kcmdr)))
1453             if is_scheduled(FCDBAS) and game.battle == game.quadrant:
1454                 unschedule(FCDBAS)    
1455         elif type ==  'K':
1456             game.state.remkl -= 1
1457         elif type ==  'S':
1458             game.state.nscrem -= 1
1459             game.state.kscmdr.invalidate()
1460             game.isatb = 0
1461             game.iscate = False
1462             unschedule(FSCMOVE)
1463             unschedule(FSCDBAS)
1464     # For each kind of enemy, finish message to player 
1465     prout(_(" destroyed."))
1466     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1467         return
1468     game.recompute()
1469     # Remove enemy ship from arrays describing local conditions
1470     for e in game.enemies:
1471         if e.location == w:
1472             e.move(None)
1473             break
1474     return
1475
1476 def targetcheck(w):
1477     "Return None if target is invalid, otherwise return a course angle."
1478     if not w.valid_sector():
1479         huh()
1480         return None
1481     delta = Coord()
1482     # FIXME: C code this was translated from is wacky -- why the sign reversal?
1483     delta.j = (w.j - game.sector.j);
1484     delta.i = (game.sector.i - w.i);
1485     if delta == Coord(0, 0):
1486         skip(1)
1487         prout(_("Spock-  \"Bridge to sickbay.  Dr. McCoy,"))
1488         prout(_("  I recommend an immediate review of"))
1489         prout(_("  the Captain's psychological profile.\""))
1490         scanner.chew()
1491         return None
1492     return delta.bearing()
1493
1494 def torps():
1495     "Launch photon torpedo salvo."
1496     tcourse = []
1497     game.ididit = False
1498     if damaged(DPHOTON):
1499         prout(_("Photon tubes damaged."))
1500         scanner.chew()
1501         return
1502     if game.torps == 0:
1503         prout(_("No torpedoes left."))
1504         scanner.chew()
1505         return
1506     # First, get torpedo count
1507     while True:
1508         scanner.next()
1509         if scanner.token == "IHALPHA":
1510             huh()
1511             return
1512         elif scanner.token == "IHEOL" or not scanner.waiting():
1513             prout(_("%d torpedoes left.") % game.torps)
1514             scanner.chew()
1515             proutn(_("Number of torpedoes to fire- "))
1516             continue    # Go back around to get a number
1517         else: # key == "IHREAL"
1518             n = scanner.int()
1519             if n <= 0: # abort command 
1520                 scanner.chew()
1521                 return
1522             if n > MAXBURST:
1523                 scanner.chew()
1524                 prout(_("Maximum of %d torpedoes per burst.") % MAXBURST)
1525                 return
1526             if n > game.torps:
1527                 scanner.chew()  # User requested more torps than available
1528                 continue        # Go back around
1529             break       # All is good, go to next stage
1530     # Next, get targets
1531     target = []
1532     for i in range(n):
1533         key = scanner.next()
1534         if i==0 and key == "IHEOL":
1535             break;      # no coordinate waiting, we will try prompting 
1536         if i==1 and key == "IHEOL":
1537             # direct all torpedoes at one target 
1538             while i < n:
1539                 target.append(target[0])
1540                 tcourse.append(tcourse[0])
1541                 i += 1
1542             break
1543         scanner.push(scanner.token)
1544         target.append(scanner.getcoord())
1545         if target[-1] == None:
1546             return
1547         tcourse.append(targetcheck(target[-1]))
1548         if tcourse[-1] == None:
1549             return
1550     scanner.chew()
1551     if len(target) == 0:
1552         # prompt for each one 
1553         for i in range(n):
1554             proutn(_("Target sector for torpedo number %d- ") % (i+1))
1555             scanner.chew()
1556             target.append(scanner.getcoord())
1557             if target[-1] == None:
1558                 return
1559             tcourse.append(targetcheck(target[-1]))
1560             if tcourse[-1] == None:
1561                 return
1562     game.ididit = True
1563     # Loop for moving <n> torpedoes 
1564     for i in range(n):
1565         if game.condition != "docked":
1566             game.torps -= 1
1567         dispersion = (randreal()+randreal())*0.5 -0.5
1568         if math.fabs(dispersion) >= 0.47:
1569             # misfire! 
1570             dispersion *= randreal(1.2, 2.2)
1571             if n > 0:
1572                 prouts(_("***TORPEDO NUMBER %d MISFIRES") % (i+1))
1573             else:
1574                 prouts(_("***TORPEDO MISFIRES."))
1575             skip(1)
1576             if i < n:
1577                 prout(_("  Remainder of burst aborted."))
1578             if withprob(0.2):
1579                 prout(_("***Photon tubes damaged by misfire."))
1580                 game.damage[DPHOTON] = game.damfac * randreal(1.0, 3.0)
1581             break
1582         if game.shldup or game.condition == "docked":
1583             dispersion *= 1.0 + 0.0001*game.shield
1584         torpedo(game.sector, tcourse[i], dispersion, number=i, nburst=n)
1585         if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
1586             return
1587     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)<=0:
1588         finish(FWON);
1589
1590 def overheat(rpow):
1591     "Check for phasers overheating."
1592     if rpow > 1500:
1593         checkburn = (rpow-1500.0)*0.00038
1594         if withprob(checkburn):
1595             prout(_("Weapons officer Sulu-  \"Phasers overheated, sir.\""))
1596             game.damage[DPHASER] = game.damfac* randreal(1.0, 2.0) * (1.0+checkburn)
1597
1598 def checkshctrl(rpow):
1599     "Check shield control."
1600     skip(1)
1601     if withprob(0.998):
1602         prout(_("Shields lowered."))
1603         return False
1604     # Something bad has happened 
1605     prouts(_("***RED ALERT!  RED ALERT!"))
1606     skip(2)
1607     hit = rpow*game.shield/game.inshld
1608     game.energy -= rpow+hit*0.8
1609     game.shield -= hit*0.2
1610     if game.energy <= 0.0:
1611         prouts(_("Sulu-  \"Captain! Shield malf***********************\""))
1612         skip(1)
1613         stars()
1614         finish(FPHASER)
1615         return True
1616     prouts(_("Sulu-  \"Captain! Shield malfunction! Phaser fire contained!\""))
1617     skip(2)
1618     prout(_("Lt. Uhura-  \"Sir, all decks reporting damage.\""))
1619     icas = randrange(int(hit*0.012))
1620     skip(1)
1621     fry(0.8*hit)
1622     if icas:
1623         skip(1)
1624         prout(_("McCoy to bridge- \"Severe radiation burns, Jim."))
1625         prout(_("  %d casualties so far.\"") % icas)
1626         game.casual += icas
1627         game.state.crew -= icas
1628     skip(1)
1629     prout(_("Phaser energy dispersed by shields."))
1630     prout(_("Enemy unaffected."))
1631     overheat(rpow)
1632     return True;
1633
1634 def hittem(hits):
1635     "Register a phaser hit on Klingons and Romulans."
1636     kk = 0
1637     w = Coord()
1638     skip(1)
1639     for (k, wham) in enumerate(hits):
1640         if wham==0:
1641             continue
1642         dustfac = randreal(0.9, 1.0)
1643         hit = wham*math.pow(dustfac,game.enemies[kk].kdist)
1644         kpini = game.enemies[kk].power
1645         kp = math.fabs(kpini)
1646         if PHASEFAC*hit < kp:
1647             kp = PHASEFAC*hit
1648         if game.enemies[kk].power < 0:
1649             game.enemies[kk].power -= -kp
1650         else:
1651             game.enemies[kk].power -= kp
1652         kpow = game.enemies[kk].power
1653         w = game.enemies[kk].location
1654         if hit > 0.005:
1655             if not damaged(DSRSENS):
1656                 boom(w)
1657             proutn(_("%d unit hit on ") % int(hit))
1658         else:
1659             proutn(_("Very small hit on "))
1660         ienm = game.quad[w.i][w.j]
1661         if ienm=='?':
1662             thing.angry = True
1663         proutn(crmena(False, ienm, "sector", w))
1664         skip(1)
1665         if kpow == 0:
1666             deadkl(w, ienm, w)
1667             if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
1668                 finish(FWON);           
1669             if game.alldone:
1670                 return
1671             kk -= 1     # don't do the increment
1672             continue
1673         else: # decide whether or not to emasculate klingon 
1674             if kpow>0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1675                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1676                 prout(_("   has just lost its firepower.\""))
1677                 game.enemies[kk].power = -kpow
1678         kk += 1
1679     return
1680
1681 def phasers():
1682     "Fire phasers at bad guys."
1683     hits = []
1684     kz = 0; k = 1; irec=0 # Cheating inhibitor 
1685     ifast = False; no = False; itarg = True; msgflag = True; rpow=0
1686     automode = "NOTSET"
1687     key=0
1688     skip(1)
1689     # SR sensors and Computer are needed for automode 
1690     if damaged(DSRSENS) or damaged(DCOMPTR):
1691         itarg = False
1692     if game.condition == "docked":
1693         prout(_("Phasers can't be fired through base shields."))
1694         scanner.chew()
1695         return
1696     if damaged(DPHASER):
1697         prout(_("Phaser control damaged."))
1698         scanner.chew()
1699         return
1700     if game.shldup:
1701         if damaged(DSHCTRL):
1702             prout(_("High speed shield control damaged."))
1703             scanner.chew()
1704             return
1705         if game.energy <= 200.0:
1706             prout(_("Insufficient energy to activate high-speed shield control."))
1707             scanner.chew()
1708             return
1709         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1710         ifast = True
1711     # Original code so convoluted, I re-did it all
1712     # (That was Tom Almy talking about the C code, I think -- ESR)
1713     while automode=="NOTSET":
1714         key=scanner.next()
1715         if key == "IHALPHA":
1716             if scanner.sees("manual"):
1717                 if len(game.enemies)==0:
1718                     prout(_("There is no enemy present to select."))
1719                     scanner.chew()
1720                     key = "IHEOL"
1721                     automode="AUTOMATIC"
1722                 else:
1723                     automode = "MANUAL"
1724                     key = scanner.next()
1725             elif scanner.sees("automatic"):
1726                 if (not itarg) and len(game.enemies) != 0:
1727                     automode = "FORCEMAN"
1728                 else:
1729                     if len(game.enemies)==0:
1730                         prout(_("Energy will be expended into space."))
1731                     automode = "AUTOMATIC"
1732                     key = scanner.next()
1733             elif scanner.sees("no"):
1734                 no = True
1735             else:
1736                 huh()
1737                 return
1738         elif key == "IHREAL":
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                 automode = "AUTOMATIC"
1746         else:
1747             # "IHEOL" 
1748             if len(game.enemies)==0:
1749                 prout(_("Energy will be expended into space."))
1750                 automode = "AUTOMATIC"
1751             elif not itarg:
1752                 automode = "FORCEMAN"
1753             else: 
1754                 proutn(_("Manual or automatic? "))
1755                 scanner.chew()
1756     avail = game.energy
1757     if ifast:
1758         avail -= 200.0
1759     if automode == "AUTOMATIC":
1760         if key == "IHALPHA" and scanner.sees("no"):
1761             no = True
1762             key = scanner.next()
1763         if key != "IHREAL" and len(game.enemies) != 0:
1764             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1765         irec=0
1766         while True:
1767             scanner.chew()
1768             if not kz:
1769                 for i in range(len(game.enemies)):
1770                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1771             kz=1
1772             proutn(_("%d units required. ") % irec)
1773             scanner.chew()
1774             proutn(_("Units to fire= "))
1775             key = scanner.next()
1776             if key!="IHREAL":
1777                 return
1778             rpow = scanner.real
1779             if rpow > avail:
1780                 proutn(_("Energy available= %.2f") % avail)
1781                 skip(1)
1782                 key = "IHEOL"
1783             if not rpow > avail:
1784                 break
1785         if rpow<=0:
1786             # chicken out 
1787             scanner.chew()
1788             return
1789         key=scanner.next()
1790         if key == "IHALPHA" and scanner.sees("no"):
1791             no = True
1792         if ifast:
1793             game.energy -= 200; # Go and do it! 
1794             if checkshctrl(rpow):
1795                 return
1796         scanner.chew()
1797         game.energy -= rpow
1798         extra = rpow
1799         if len(game.enemies):
1800             extra = 0.0
1801             powrem = rpow
1802             for i in range(len(game.enemies)):
1803                 hits.append(0.0)
1804                 if powrem <= 0:
1805                     continue
1806                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90,game.enemies[i].kdist))
1807                 over = randreal(1.01, 1.06) * hits[i]
1808                 temp = powrem
1809                 powrem -= hits[i] + over
1810                 if powrem <= 0 and temp < hits[i]:
1811                     hits[i] = temp
1812                 if powrem <= 0:
1813                     over = 0.0
1814                 extra += over
1815             if powrem > 0.0:
1816                 extra += powrem
1817             hittem(hits)
1818             game.ididit = True
1819         if extra > 0 and not game.alldone:
1820             if game.tholian:
1821                 proutn(_("*** Tholian web absorbs "))
1822                 if len(game.enemies)>0:
1823                     proutn(_("excess "))
1824                 prout(_("phaser energy."))
1825             else:
1826                 prout(_("%d expended on empty space.") % int(extra))
1827     elif automode == "FORCEMAN":
1828         scanner.chew()
1829         key = "IHEOL"
1830         if damaged(DCOMPTR):
1831             prout(_("Battle computer damaged, manual fire only."))
1832         else:
1833             skip(1)
1834             prouts(_("---WORKING---"))
1835             skip(1)
1836             prout(_("Short-range-sensors-damaged"))
1837             prout(_("Insufficient-data-for-automatic-phaser-fire"))
1838             prout(_("Manual-fire-must-be-used"))
1839             skip(1)
1840     elif automode == "MANUAL":
1841         rpow = 0.0
1842         for k in range(len(game.enemies)):
1843             aim = game.enemies[k].location
1844             ienm = game.quad[aim.i][aim.j]
1845             if msgflag:
1846                 proutn(_("Energy available= %.2f") % (avail-0.006))
1847                 skip(1)
1848                 msgflag = False
1849                 rpow = 0.0
1850             if damaged(DSRSENS) and \
1851                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
1852                 prout(cramen(ienm) + _(" can't be located without short range scan."))
1853                 scanner.chew()
1854                 key = "IHEOL"
1855                 hits[k] = 0; # prevent overflow -- thanks to Alexei Voitenko 
1856                 k += 1
1857                 continue
1858             if key == "IHEOL":
1859                 scanner.chew()
1860                 if itarg and k > kz:
1861                     irec=(abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9,game.enemies[k].kdist))) *  randreal(1.01, 1.06) + 1.0
1862                 kz = k
1863                 proutn("(")
1864                 if not damaged(DCOMPTR):
1865                     proutn("%d" % irec)
1866                 else:
1867                     proutn("??")
1868                 proutn(")  ")
1869                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))                
1870                 key = scanner.next()
1871             if key == "IHALPHA" and scanner.sees("no"):
1872                 no = True
1873                 key = scanner.next()
1874                 continue
1875             if key == "IHALPHA":
1876                 huh()
1877                 return
1878             if key == "IHEOL":
1879                 if k==1: # Let me say I'm baffled by this 
1880                     msgflag = True
1881                 continue
1882             if scanner.real < 0:
1883                 # abort out 
1884                 scanner.chew()
1885                 return
1886             hits[k] = scanner.real
1887             rpow += scanner.real
1888             # If total requested is too much, inform and start over 
1889             if rpow > avail:
1890                 prout(_("Available energy exceeded -- try again."))
1891                 scanner.chew()
1892                 return
1893             key = scanner.next(); # scan for next value 
1894             k += 1
1895         if rpow == 0.0:
1896             # zero energy -- abort 
1897             scanner.chew()
1898             return
1899         if key == "IHALPHA" and scanner.sees("no"):
1900             no = True
1901         game.energy -= rpow
1902         scanner.chew()
1903         if ifast:
1904             game.energy -= 200.0
1905             if checkshctrl(rpow):
1906                 return
1907         hittem(hits)
1908         game.ididit = True
1909      # Say shield raised or malfunction, if necessary 
1910     if game.alldone:
1911         return
1912     if ifast:
1913         skip(1)
1914         if no == 0:
1915             if withprob(0.01):
1916                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
1917                 prouts(_("         CLICK   CLICK   POP  . . ."))
1918                 prout(_(" No response, sir!"))
1919                 game.shldup = False
1920             else:
1921                 prout(_("Shields raised."))
1922         else:
1923             game.shldup = False
1924     overheat(rpow);
1925
1926 # Code from events,c begins here.
1927
1928 # This isn't a real event queue a la BSD Trek yet -- you can only have one 
1929 # event of each type active at any given time.  Mostly these means we can 
1930 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
1931 # BSD Trek, from which we swiped the idea, can have up to 5.
1932
1933 def unschedule(evtype):
1934     "Remove an event from the schedule."
1935     game.future[evtype].date = FOREVER
1936     return game.future[evtype]
1937
1938 def is_scheduled(evtype):
1939     "Is an event of specified type scheduled."
1940     return game.future[evtype].date != FOREVER
1941
1942 def scheduled(evtype):
1943     "When will this event happen?"
1944     return game.future[evtype].date
1945
1946 def schedule(evtype, offset):
1947     "Schedule an event of specified type."
1948     game.future[evtype].date = game.state.date + offset
1949     return game.future[evtype]
1950
1951 def postpone(evtype, offset):
1952     "Postpone a scheduled event."
1953     game.future[evtype].date += offset
1954
1955 def cancelrest():
1956     "Rest period is interrupted by event."
1957     if game.resting:
1958         skip(1)
1959         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
1960         if ja():
1961             game.resting = False
1962             game.optime = 0.0
1963             return True
1964     return False
1965
1966 def events():
1967     "Run through the event queue looking for things to do."
1968     i=0
1969     fintim = game.state.date + game.optime; yank=0
1970     ictbeam = False; istract = False
1971     w = Coord(); hold = Coord()
1972     ev = Event(); ev2 = Event()
1973
1974     def tractorbeam(yank):
1975         "Tractor-beaming cases merge here." 
1976         announce()
1977         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5) 
1978         skip(1)
1979         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
1980         # If Kirk & Co. screwing around on planet, handle 
1981         atover(True) # atover(true) is Grab 
1982         if game.alldone:
1983             return
1984         if game.icraft: # Caught in Galileo? 
1985             finish(FSTRACTOR)
1986             return
1987         # Check to see if shuttle is aboard 
1988         if game.iscraft == "offship":
1989             skip(1)
1990             if withprob(0.5):
1991                 prout(_("Galileo, left on the planet surface, is captured"))
1992                 prout(_("by aliens and made into a flying McDonald's."))
1993                 game.damage[DSHUTTL] = -10
1994                 game.iscraft = "removed"
1995             else:
1996                 prout(_("Galileo, left on the planet surface, is well hidden."))
1997         if evcode == FSPY:
1998             game.quadrant = game.state.kscmdr
1999         else:
2000             game.quadrant = game.state.kcmdr[i]
2001         game.sector = randplace(QUADSIZE)
2002         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2003                % (game.quadrant, game.sector))
2004         if game.resting:
2005             prout(_("(Remainder of rest/repair period cancelled.)"))
2006             game.resting = False
2007         if not game.shldup:
2008             if not damaged(DSHIELD) and game.shield > 0:
2009                 doshield(shraise=True) # raise shields 
2010                 game.shldchg = False
2011             else:
2012                 prout(_("(Shields not currently useable.)"))
2013         newqad()
2014         # Adjust finish time to time of tractor beaming 
2015         fintim = game.state.date+game.optime
2016         attack(torps_ok=False)
2017         if not game.state.kcmdr:
2018             unschedule(FTBEAM)
2019         else: 
2020             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2021
2022     def destroybase():
2023         "Code merges here for any commander destroying a starbase." 
2024         # Not perfect, but will have to do 
2025         # Handle case where base is in same quadrant as starship 
2026         if game.battle == game.quadrant:
2027             game.state.chart[game.battle.i][game.battle.j].starbase = False
2028             game.quad[game.base.i][game.base.j] = '.'
2029             game.base.invalidate()
2030             newcnd()
2031             skip(1)
2032             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2033         elif game.state.baseq and communicating():
2034             # Get word via subspace radio 
2035             announce()
2036             skip(1)
2037             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2038             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2039             if game.isatb == 2: 
2040                 prout(_("the Klingon Super-Commander"))
2041             else:
2042                 prout(_("a Klingon Commander"))
2043             game.state.chart[game.battle.i][game.battle.j].starbase = False
2044         # Remove Starbase from galaxy 
2045         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2046         game.state.baseq = filter(lambda x: x != game.battle, game.state.baseq)
2047         if game.isatb == 2:
2048             # reinstate a commander's base attack 
2049             game.battle = hold
2050             game.isatb = 0
2051         else:
2052             game.battle.invalidate()
2053     if game.idebug:
2054         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2055         for i in range(1, NEVENTS):
2056             if   i == FSNOVA:  proutn("=== Supernova       ")
2057             elif i == FTBEAM:  proutn("=== T Beam          ")
2058             elif i == FSNAP:   proutn("=== Snapshot        ")
2059             elif i == FBATTAK: proutn("=== Base Attack     ")
2060             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2061             elif i == FSCMOVE: proutn("=== SC Move         ")
2062             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2063             elif i == FDSPROB: proutn("=== Probe Move      ")
2064             elif i == FDISTR:  proutn("=== Distress Call   ")
2065             elif i == FENSLV:  proutn("=== Enslavement     ")
2066             elif i == FREPRO:  proutn("=== Klingon Build   ")
2067             if is_scheduled(i):
2068                 prout("%.2f" % (scheduled(i)))
2069             else:
2070                 prout("never")
2071     radio_was_broken = damaged(DRADIO)
2072     hold.i = hold.j = 0
2073     while True:
2074         # Select earliest extraneous event, evcode==0 if no events 
2075         evcode = FSPY
2076         if game.alldone:
2077             return
2078         datemin = fintim
2079         for l in range(1, NEVENTS):
2080             if game.future[l].date < datemin:
2081                 evcode = l
2082                 if game.idebug:
2083                     prout("== Event %d fires" % evcode)
2084                 datemin = game.future[l].date
2085         xtime = datemin-game.state.date
2086         game.state.date = datemin
2087         # Decrement Federation resources and recompute remaining time 
2088         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2089         game.recompute()
2090         if game.state.remtime <=0:
2091             finish(FDEPLETE)
2092             return
2093         # Any crew left alive? 
2094         if game.state.crew <=0:
2095             finish(FCREW)
2096             return
2097         # Is life support adequate? 
2098         if damaged(DLIFSUP) and game.condition != "docked":
2099             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2100                 finish(FLIFESUP)
2101                 return
2102             game.lsupres -= xtime
2103             if game.damage[DLIFSUP] <= xtime:
2104                 game.lsupres = game.inlsr
2105         # Fix devices 
2106         repair = xtime
2107         if game.condition == "docked":
2108             repair /= DOCKFAC
2109         # Don't fix Deathray here 
2110         for l in range(NDEVICES):
2111             if game.damage[l] > 0.0 and l != DDRAY:
2112                 if game.damage[l]-repair > 0.0:
2113                     game.damage[l] -= repair
2114                 else:
2115                     game.damage[l] = 0.0
2116         # If radio repaired, update star chart and attack reports 
2117         if radio_was_broken and not damaged(DRADIO):
2118             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2119             prout(_("   surveillance reports are coming in."))
2120             skip(1)
2121             if not game.iseenit:
2122                 attackreport(False)
2123                 game.iseenit = True
2124             rechart()
2125             prout(_("   The star chart is now up to date.\""))
2126             skip(1)
2127         # Cause extraneous event EVCODE to occur 
2128         game.optime -= xtime
2129         if evcode == FSNOVA: # Supernova 
2130             announce()
2131             supernova(None)
2132             schedule(FSNOVA, expran(0.5*game.intime))
2133             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2134                 return
2135         elif evcode == FSPY: # Check with spy to see if SC should tractor beam 
2136             if game.state.nscrem == 0 or \
2137                 ictbeam or istract or \
2138                 game.condition=="docked" or game.isatb==1 or game.iscate:
2139                 return
2140             if game.ientesc or \
2141                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2142                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2143                 (damaged(DSHIELD) and \
2144                  (game.energy < 2500 or damaged(DPHASER)) and \
2145                  (game.torps < 5 or damaged(DPHOTON))):
2146                 # Tractor-beam her! 
2147                 istract = ictbeam = True
2148                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2149             else:
2150                 return
2151         elif evcode == FTBEAM: # Tractor beam 
2152             if not game.state.kcmdr:
2153                 unschedule(FTBEAM)
2154                 continue
2155             i = randrange(len(game.state.kcmdr))
2156             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2157             if istract or game.condition == "docked" or yank == 0:
2158                 # Drats! Have to reschedule 
2159                 schedule(FTBEAM, 
2160                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2161                 continue
2162             ictbeam = True
2163             tractorbeam(yank)
2164         elif evcode == FSNAP: # Snapshot of the universe (for time warp) 
2165             game.snapsht = copy.deepcopy(game.state)
2166             game.state.snap = True
2167             schedule(FSNAP, expran(0.5 * game.intime))
2168         elif evcode == FBATTAK: # Commander attacks starbase 
2169             if not game.state.kcmdr or not game.state.baseq:
2170                 # no can do 
2171                 unschedule(FBATTAK)
2172                 unschedule(FCDBAS)
2173                 continue
2174             try:
2175                 for ibq in game.state.baseq:
2176                    for cmdr in game.state.kcmdr: 
2177                        if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2178                            raise ibq
2179                 else:
2180                     # no match found -- try later 
2181                     schedule(FBATTAK, expran(0.3*game.intime))
2182                     unschedule(FCDBAS)
2183                     continue
2184             except Coord:
2185                 pass
2186             # commander + starbase combination found -- launch attack 
2187             game.battle = ibq
2188             schedule(FCDBAS, randreal(1.0, 4.0))
2189             if game.isatb: # extra time if SC already attacking 
2190                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2191             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2192             game.iseenit = False
2193             if not communicating():
2194                 continue # No warning :-( 
2195             game.iseenit = True
2196             announce()
2197             skip(1)
2198             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2199             prout(_("   reports that it is under attack and that it can"))
2200             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2201             if cancelrest():
2202                 return
2203         elif evcode == FSCDBAS: # Supercommander destroys base 
2204             unschedule(FSCDBAS)
2205             game.isatb = 2
2206             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase: 
2207                 continue # WAS RETURN! 
2208             hold = game.battle
2209             game.battle = game.state.kscmdr
2210             destroybase()
2211         elif evcode == FCDBAS: # Commander succeeds in destroying base 
2212             if evcode==FCDBAS:
2213                 unschedule(FCDBAS)
2214                 if not game.state.baseq() \
2215                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2216                     game.battle.invalidate()
2217                     continue
2218                 # find the lucky pair 
2219                 for cmdr in game.state.kcmdr:
2220                     if cmdr == game.battle: 
2221                         break
2222                 else:
2223                     # No action to take after all 
2224                     continue
2225             destroybase()
2226         elif evcode == FSCMOVE: # Supercommander moves 
2227             schedule(FSCMOVE, 0.2777)
2228             if not game.ientesc and not istract and game.isatb != 1 and \
2229                    (not game.iscate or not game.justin): 
2230                 supercommander()
2231         elif evcode == FDSPROB: # Move deep space probe 
2232             schedule(FDSPROB, 0.01)
2233             if not game.probe.next():
2234                 if not game.probe.quadrant().valid_quadrant() or \
2235                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2236                     # Left galaxy or ran into supernova
2237                     if communicating():
2238                         announce()
2239                         skip(1)
2240                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2241                         if not game.probe.quadrant().valid_quadrant():
2242                             prout(_("has left the galaxy.\""))
2243                         else:
2244                             prout(_("is no longer transmitting.\""))
2245                     unschedule(FDSPROB)
2246                     continue
2247                 if communicating():
2248                     #announce()
2249                     skip(1)
2250                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2251             pdest = game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j]
2252             if communicating():
2253                 chp = game.state.chart[game.probe.quadrant().i][game.probe.quadrant().j]
2254                 chp.klingons = pdest.klingons
2255                 chp.starbase = pdest.starbase
2256                 chp.stars = pdest.stars
2257                 pdest.charted = True
2258             game.probe.moves -= 1 # One less to travel
2259             if game.probe.arrived() and game.isarmed and pdest.stars:
2260                 supernova(game.probe)           # fire in the hole!
2261                 unschedule(FDSPROB)
2262                 if game.state.galaxy[game.quadrant().i][game.quadrant().j].supernova: 
2263                     return
2264         elif evcode == FDISTR: # inhabited system issues distress call 
2265             unschedule(FDISTR)
2266             # try a whole bunch of times to find something suitable 
2267             for i in range(100):
2268                 # need a quadrant which is not the current one,
2269                 # which has some stars which are inhabited and
2270                 # not already under attack, which is not
2271                 # supernova'ed, and which has some Klingons in it
2272                 w = randplace(GALSIZE)
2273                 q = game.state.galaxy[w.i][w.j]
2274                 if not (game.quadrant == w or q.planet == None or \
2275                       not q.planet.inhabited or \
2276                       q.supernova or q.status!="secure" or q.klingons<=0):
2277                     break
2278             else:
2279                 # can't seem to find one; ignore this call 
2280                 if game.idebug:
2281                     prout("=== Couldn't find location for distress event.")
2282                 continue
2283             # got one!!  Schedule its enslavement 
2284             ev = schedule(FENSLV, expran(game.intime))
2285             ev.quadrant = w
2286             q.status = "distressed"
2287             # tell the captain about it if we can 
2288             if communicating():
2289                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2290                         % (q.planet, `w`))
2291                 prout(_("by a Klingon invasion fleet."))
2292                 if cancelrest():
2293                     return
2294         elif evcode == FENSLV:          # starsystem is enslaved 
2295             ev = unschedule(FENSLV)
2296             # see if current distress call still active 
2297             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2298             if q.klingons <= 0:
2299                 q.status = "secure"
2300                 continue
2301             q.status = "enslaved"
2302
2303             # play stork and schedule the first baby 
2304             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2305             ev2.quadrant = ev.quadrant
2306
2307             # report the disaster if we can 
2308             if communicating():
2309                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2310                         q.planet)
2311                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2312         elif evcode == FREPRO:          # Klingon reproduces 
2313             # If we ever switch to a real event queue, we'll need to
2314             # explicitly retrieve and restore the x and y.
2315             ev = schedule(FREPRO, expran(1.0 * game.intime))
2316             # see if current distress call still active 
2317             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2318             if q.klingons <= 0:
2319                 q.status = "secure"
2320                 continue
2321             if game.state.remkl >=MAXKLGAME:
2322                 continue                # full right now 
2323             # reproduce one Klingon 
2324             w = ev.quadrant
2325             m = Coord()
2326             if game.klhere >= MAXKLQUAD:
2327                 try:
2328                     # this quadrant not ok, pick an adjacent one 
2329                     for m.i in range(w.i - 1, w.i + 2):
2330                         for m.j in range(w.j - 1, w.j + 2):
2331                             if not m.valid_quadrant():
2332                                 continue
2333                             q = game.state.galaxy[m.i][m.j]
2334                             # check for this quad ok (not full & no snova) 
2335                             if q.klingons >= MAXKLQUAD or q.supernova:
2336                                 continue
2337                             raise JumpOut
2338                     else:
2339                         continue        # search for eligible quadrant failed
2340                 except JumpOut:
2341                     w = m
2342             # deliver the child 
2343             game.state.remkl += 1
2344             q.klingons += 1
2345             if game.quadrant == w:
2346                 game.klhere += 1
2347                 game.enemies.append(newkling())
2348             # recompute time left
2349             game.recompute()
2350             if communicating():
2351                 if game.quadrant == w:
2352                     prout(_("Spock- sensors indicate the Klingons have"))
2353                     prout(_("launched a warship from %s.") % q.planet)
2354                 else:
2355                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2356                     if q.planet != None:
2357                         proutn(_("near %s ") % q.planet)
2358                     prout(_("in Quadrant %s.") % w)
2359                                 
2360 def wait():
2361     "Wait on events."
2362     game.ididit = False
2363     while True:
2364         key = scanner.next()
2365         if key  != "IHEOL":
2366             break
2367         proutn(_("How long? "))
2368     scanner.chew()
2369     if key != "IHREAL":
2370         huh()
2371         return
2372     origTime = delay = scanner.real
2373     if delay <= 0.0:
2374         return
2375     if delay >= game.state.remtime or len(game.enemies) != 0:
2376         proutn(_("Are you sure? "))
2377         if not ja():
2378             return
2379     # Alternate resting periods (events) with attacks 
2380     game.resting = True
2381     while True:
2382         if delay <= 0:
2383             game.resting = False
2384         if not game.resting:
2385             prout(_("%d stardates left.") % int(game.state.remtime))
2386             return
2387         temp = game.optime = delay
2388         if len(game.enemies):
2389             rtime = randreal(1.0, 2.0)
2390             if rtime < temp:
2391                 temp = rtime
2392             game.optime = temp
2393         if game.optime < delay:
2394             attack(torps_ok=False)
2395         if game.alldone:
2396             return
2397         events()
2398         game.ididit = True
2399         if game.alldone:
2400             return
2401         delay -= temp
2402         # Repair Deathray if long rest at starbase 
2403         if origTime-delay >= 9.99 and game.condition == "docked":
2404             game.damage[DDRAY] = 0.0
2405         # leave if quadrant supernovas
2406         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2407             break
2408     game.resting = False
2409     game.optime = 0
2410
2411 def nova(nov):
2412     "Star goes nova." 
2413     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2414     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2415     if withprob(0.05):
2416         # Wow! We've supernova'ed 
2417         supernova(game.quadrant)
2418         return
2419     # handle initial nova 
2420     game.quad[nov.i][nov.j] = '.'
2421     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2422     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2423     game.state.starkl += 1
2424     # Set up queue to recursively trigger adjacent stars 
2425     hits = [nov]
2426     kount = 0
2427     while hits:
2428         offset = Coord()
2429         start = hits.pop()
2430         for offset.i in range(-1, 1+1):
2431             for offset.j in range(-1, 1+1):
2432                 if offset.j==0 and offset.i==0:
2433                     continue
2434                 neighbor = start + offset
2435                 if not neighbor.valid_sector():
2436                     continue
2437                 iquad = game.quad[neighbor.i][neighbor.j]
2438                 # Empty space ends reaction
2439                 if iquad in ('.', '?', ' ', 'T', '#'):
2440                     pass
2441                 elif iquad == '*': # Affect another star 
2442                     if withprob(0.05):
2443                         # This star supernovas 
2444                         supernova(game.quadrant)
2445                         return
2446                     else:
2447                         hits.append(neighbor)
2448                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2449                         game.state.starkl += 1
2450                         proutn(crmena(True, '*', "sector", neighbor))
2451                         prout(_(" novas."))
2452                         game.quad[neighbor.i][neighbor.j] = '.'
2453                         kount += 1
2454                 elif iquad in ('P', '@'): # Destroy planet 
2455                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2456                     if iquad == 'P':
2457                         game.state.nplankl += 1
2458                     else:
2459                         game.state.worldkl += 1
2460                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2461                     game.iplnet.pclass = "destroyed"
2462                     game.iplnet = None
2463                     game.plnet.invalidate()
2464                     if game.landed:
2465                         finish(FPNOVA)
2466                         return
2467                     game.quad[neighbor.i][neighbor.j] = '.'
2468                 elif iquad == 'B': # Destroy base 
2469                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2470                     game.state.baseq = filter(lambda x: x!= game.quadrant, game.state.baseq)
2471                     game.base.invalidate()
2472                     game.state.basekl += 1
2473                     newcnd()
2474                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2475                     game.quad[neighbor.i][neighbor.j] = '.'
2476                 elif iquad in ('E', 'F'): # Buffet ship 
2477                     prout(_("***Starship buffeted by nova."))
2478                     if game.shldup:
2479                         if game.shield >= 2000.0:
2480                             game.shield -= 2000.0
2481                         else:
2482                             diff = 2000.0 - game.shield
2483                             game.energy -= diff
2484                             game.shield = 0.0
2485                             game.shldup = False
2486                             prout(_("***Shields knocked out."))
2487                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2488                     else:
2489                         game.energy -= 2000.0
2490                     if game.energy <= 0:
2491                         finish(FNOVA)
2492                         return
2493                     # add in course nova contributes to kicking starship
2494                     bump += (game.sector-hits[mm]).sgn()
2495                 elif iquad == 'K': # kill klingon 
2496                     deadkl(neighbor, iquad, neighbor)
2497                 elif iquad in ('C','S','R'): # Damage/destroy big enemies 
2498                     for ll in range(len(game.enemies)):
2499                         if game.enemies[ll].location == neighbor:
2500                             break
2501                     game.enemies[ll].power -= 800.0 # If firepower is lost, die 
2502                     if game.enemies[ll].power <= 0.0:
2503                         deadkl(neighbor, iquad, neighbor)
2504                         break
2505                     newc = neighbor + neighbor - hits[mm]
2506                     proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2507                     if not newc.valid_sector():
2508                         # can't leave quadrant 
2509                         skip(1)
2510                         break
2511                     iquad1 = game.quad[newc.i][newc.j]
2512                     if iquad1 == ' ':
2513                         proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2514                         skip(1)
2515                         deadkl(neighbor, iquad, newc)
2516                         break
2517                     if iquad1 != '.':
2518                         # can't move into something else 
2519                         skip(1)
2520                         break
2521                     proutn(_(", buffeted to Sector %s") % newc)
2522                     game.quad[neighbor.i][neighbor.j] = '.'
2523                     game.quad[newc.i][newc.j] = iquad
2524                     game.enemies[ll].move(newc)
2525     # Starship affected by nova -- kick it away. 
2526     dist = kount*0.1
2527     direc = ncourse[3*(bump.i+1)+bump.j+2]
2528     if direc == 0.0:
2529         dist = 0.0
2530     if dist == 0.0:
2531         return
2532     scourse = course(bearing=direc, distance=dist)
2533     game.optime = scourse.time(warp=4)
2534     skip(1)
2535     prout(_("Force of nova displaces starship."))
2536     imove(scourse, noattack=True)
2537     game.optime = scourse.time(warp=4)
2538     return
2539         
2540 def supernova(w):
2541     "Star goes supernova."
2542     num = 0; npdead = 0
2543     if w != None: 
2544         nq = copy.copy(w)
2545     else:
2546         # Scheduled supernova -- select star at random. 
2547         stars = 0
2548         nq = Coord()
2549         for nq.i in range(GALSIZE):
2550             for nq.j in range(GALSIZE):
2551                 stars += game.state.galaxy[nq.i][nq.j].stars
2552         if stars == 0:
2553             return # nothing to supernova exists 
2554         num = randrange(stars) + 1
2555         for nq.i in range(GALSIZE):
2556             for nq.j in range(GALSIZE):
2557                 num -= game.state.galaxy[nq.i][nq.j].stars
2558                 if num <= 0:
2559                     break
2560             if num <=0:
2561                 break
2562         if game.idebug:
2563             proutn("=== Super nova here?")
2564             if ja():
2565                 nq = game.quadrant
2566     if not nq == game.quadrant or game.justin:
2567         # it isn't here, or we just entered (treat as enroute) 
2568         if communicating():
2569             skip(1)
2570             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2571             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2572     else:
2573         ns = Coord()
2574         # we are in the quadrant! 
2575         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2576         for ns.i in range(QUADSIZE):
2577             for ns.j in range(QUADSIZE):
2578                 if game.quad[ns.i][ns.j]=='*':
2579                     num -= 1
2580                     if num==0:
2581                         break
2582             if num==0:
2583                 break
2584         skip(1)
2585         prouts(_("***RED ALERT!  RED ALERT!"))
2586         skip(1)
2587         prout(_("***Incipient supernova detected at Sector %s") % ns)
2588         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2589             proutn(_("Emergency override attempts t"))
2590             prouts("***************")
2591             skip(1)
2592             stars()
2593             game.alldone = True
2594     # destroy any Klingons in supernovaed quadrant
2595     kldead = game.state.galaxy[nq.i][nq.j].klingons
2596     game.state.galaxy[nq.i][nq.j].klingons = 0
2597     if nq == game.state.kscmdr:
2598         # did in the Supercommander! 
2599         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2600         game.iscate = False
2601         unschedule(FSCMOVE)
2602         unschedule(FSCDBAS)
2603     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2604     comkills = len(game.state.kcmdr) - len(survivors)
2605     game.state.kcmdr = survivors
2606     kldead -= comkills
2607     if not game.state.kcmdr:
2608         unschedule(FTBEAM)
2609     game.state.remkl -= kldead
2610     # destroy Romulans and planets in supernovaed quadrant 
2611     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2612     game.state.galaxy[nq.i][nq.j].romulans = 0
2613     game.state.nromrem -= nrmdead
2614     # Destroy planets 
2615     for loop in range(game.inplan):
2616         if game.state.planets[loop].quadrant == nq:
2617             game.state.planets[loop].pclass = "destroyed"
2618             npdead += 1
2619     # Destroy any base in supernovaed quadrant
2620     game.state.baseq = filter(lambda x: x != nq, game.state.baseq)
2621     # If starship caused supernova, tally up destruction 
2622     if w != None:
2623         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2624         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2625         game.state.nplankl += npdead
2626     # mark supernova in galaxy and in star chart 
2627     if game.quadrant == nq or communicating():
2628         game.state.galaxy[nq.i][nq.j].supernova = True
2629     # If supernova destroys last Klingons give special message 
2630     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0 and not nq == game.quadrant:
2631         skip(2)
2632         if w == None:
2633             prout(_("Lucky you!"))
2634         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2635         finish(FWON)
2636         return
2637     # if some Klingons remain, continue or die in supernova 
2638     if game.alldone:
2639         finish(FSNOVAED)
2640     return
2641
2642 # Code from finish.c ends here.
2643
2644 def selfdestruct():
2645     "Self-destruct maneuver. Finish with a BANG!" 
2646     scanner.chew()
2647     if damaged(DCOMPTR):
2648         prout(_("Computer damaged; cannot execute destruct sequence."))
2649         return
2650     prouts(_("---WORKING---")); skip(1)
2651     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2652     prouts("   10"); skip(1)
2653     prouts("       9"); skip(1)
2654     prouts("          8"); skip(1)
2655     prouts("             7"); skip(1)
2656     prouts("                6"); skip(1)
2657     skip(1)
2658     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2659     skip(1)
2660     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2661     skip(1)
2662     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2663     skip(1)
2664     scanner.next()
2665     scanner.chew()
2666     if game.passwd != scanner.token:
2667         prouts(_("PASSWORD-REJECTED;"))
2668         skip(1)
2669         prouts(_("CONTINUITY-EFFECTED"))
2670         skip(2)
2671         return
2672     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2673     prouts("                   5"); skip(1)
2674     prouts("                      4"); skip(1)
2675     prouts("                         3"); skip(1)
2676     prouts("                            2"); skip(1)
2677     prouts("                              1"); skip(1)
2678     if withprob(0.15):
2679         prouts(_("GOODBYE-CRUEL-WORLD"))
2680         skip(1)
2681     kaboom()
2682
2683 def kaboom():
2684     stars()
2685     if game.ship=='E':
2686         prouts("***")
2687     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2688     skip(1)
2689     stars()
2690     skip(1)
2691     if len(game.enemies) != 0:
2692         whammo = 25.0 * game.energy
2693         l=1
2694         while l <= len(game.enemies):
2695             if game.enemies[l].power*game.enemies[l].kdist <= whammo: 
2696                 deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
2697             l += 1
2698     finish(FDILITHIUM)
2699                                 
2700 def killrate():
2701     "Compute our rate of kils over time."
2702     elapsed = game.state.date - game.indate
2703     if elapsed == 0:    # Avoid divide-by-zero error if calculated on turn 0
2704         return 0
2705     else:
2706         starting = (game.inkling + game.incom + game.inscom)
2707         remaining = (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)
2708         return (starting - remaining)/elapsed
2709
2710 def badpoints():
2711     "Compute demerits."
2712     badpt = 5.0*game.state.starkl + \
2713             game.casual + \
2714             10.0*game.state.nplankl + \
2715             300*game.state.nworldkl + \
2716             45.0*game.nhelp +\
2717             100.0*game.state.basekl +\
2718             3.0*game.abandoned
2719     if game.ship == 'F':
2720         badpt += 100.0
2721     elif game.ship == None:
2722         badpt += 200.0
2723     return badpt
2724
2725 def finish(ifin):
2726     # end the game, with appropriate notfications 
2727     igotit = False
2728     game.alldone = True
2729     skip(3)
2730     prout(_("It is stardate %.1f.") % game.state.date)
2731     skip(1)
2732     if ifin == FWON: # Game has been won
2733         if game.state.nromrem != 0:
2734             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2735                   game.state.nromrem)
2736
2737         prout(_("You have smashed the Klingon invasion fleet and saved"))
2738         prout(_("the Federation."))
2739         game.gamewon = True
2740         if game.alive:
2741             badpt = badpoints()
2742             if badpt < 100.0:
2743                 badpt = 0.0     # Close enough!
2744             # killsPerDate >= RateMax
2745             if game.state.date-game.indate < 5.0 or \
2746                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2747                 skip(1)
2748                 prout(_("In fact, you have done so well that Starfleet Command"))
2749                 if game.skill == SKILL_NOVICE:
2750                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2751                 elif game.skill == SKILL_FAIR:
2752                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
2753                 elif game.skill == SKILL_GOOD:
2754                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
2755                 elif game.skill == SKILL_EXPERT:
2756                     prout(_("promotes you to Commodore Emeritus."))
2757                     skip(1)
2758                     prout(_("Now that you think you're really good, try playing"))
2759                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
2760                 elif game.skill == SKILL_EMERITUS:
2761                     skip(1)
2762                     proutn(_("Computer-  "))
2763                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
2764                     skip(2)
2765                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
2766                     skip(1)
2767                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2768                     skip(1)
2769                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2770                     skip(1)
2771                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
2772                     skip(1)
2773                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
2774                     skip(2)
2775                     prout(_("Now you can retire and write your own Star Trek game!"))
2776                     skip(1)
2777                 elif game.skill >= SKILL_EXPERT:
2778                     if game.thawed and not game.idebug:
2779                         prout(_("You cannot get a citation, so..."))
2780                     else:
2781                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
2782                         scanner.chew()
2783                         if ja():
2784                             igotit = True
2785             # Only grant long life if alive (original didn't!)
2786             skip(1)
2787             prout(_("LIVE LONG AND PROSPER."))
2788         score()
2789         if igotit:
2790             plaque()        
2791         return
2792     elif ifin == FDEPLETE: # Federation Resources Depleted
2793         prout(_("Your time has run out and the Federation has been"))
2794         prout(_("conquered.  Your starship is now Klingon property,"))
2795         prout(_("and you are put on trial as a war criminal.  On the"))
2796         proutn(_("basis of your record, you are "))
2797         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)*3.0 > (game.inkling + game.incom + game.inscom):
2798             prout(_("acquitted."))
2799             skip(1)
2800             prout(_("LIVE LONG AND PROSPER."))
2801         else:
2802             prout(_("found guilty and"))
2803             prout(_("sentenced to death by slow torture."))
2804             game.alive = False
2805         score()
2806         return
2807     elif ifin == FLIFESUP:
2808         prout(_("Your life support reserves have run out, and"))
2809         prout(_("you die of thirst, starvation, and asphyxiation."))
2810         prout(_("Your starship is a derelict in space."))
2811     elif ifin == FNRG:
2812         prout(_("Your energy supply is exhausted."))
2813         skip(1)
2814         prout(_("Your starship is a derelict in space."))
2815     elif ifin == FBATTLE:
2816         prout(_("The %s has been destroyed in battle.") % crmshp())
2817         skip(1)
2818         prout(_("Dulce et decorum est pro patria mori."))
2819     elif ifin == FNEG3:
2820         prout(_("You have made three attempts to cross the negative energy"))
2821         prout(_("barrier which surrounds the galaxy."))
2822         skip(1)
2823         prout(_("Your navigation is abominable."))
2824         score()
2825     elif ifin == FNOVA:
2826         prout(_("Your starship has been destroyed by a nova."))
2827         prout(_("That was a great shot."))
2828         skip(1)
2829     elif ifin == FSNOVAED:
2830         prout(_("The %s has been fried by a supernova.") % crmshp())
2831         prout(_("...Not even cinders remain..."))
2832     elif ifin == FABANDN:
2833         prout(_("You have been captured by the Klingons. If you still"))
2834         prout(_("had a starbase to be returned to, you would have been"))
2835         prout(_("repatriated and given another chance. Since you have"))
2836         prout(_("no starbases, you will be mercilessly tortured to death."))
2837     elif ifin == FDILITHIUM:
2838         prout(_("Your starship is now an expanding cloud of subatomic particles"))
2839     elif ifin == FMATERIALIZE:
2840         prout(_("Starbase was unable to re-materialize your starship."))
2841         prout(_("Sic transit gloria mundi"))
2842     elif ifin == FPHASER:
2843         prout(_("The %s has been cremated by its own phasers.") % crmshp())
2844     elif ifin == FLOST:
2845         prout(_("You and your landing party have been"))
2846         prout(_("converted to energy, disipating through space."))
2847     elif ifin == FMINING:
2848         prout(_("You are left with your landing party on"))
2849         prout(_("a wild jungle planet inhabited by primitive cannibals."))
2850         skip(1)
2851         prout(_("They are very fond of \"Captain Kirk\" soup."))
2852         skip(1)
2853         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2854     elif ifin == FDPLANET:
2855         prout(_("You and your mining party perish."))
2856         skip(1)
2857         prout(_("That was a great shot."))
2858         skip(1)
2859     elif ifin == FSSC:
2860         prout(_("The Galileo is instantly annihilated by the supernova."))
2861         prout(_("You and your mining party are atomized."))
2862         skip(1)
2863         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2864         prout(_("joins the Romulans, wreaking terror on the Federation."))
2865     elif ifin == FPNOVA:
2866         prout(_("You and your mining party are atomized."))
2867         skip(1)
2868         prout(_("Mr. Spock takes command of the %s and") % crmshp())
2869         prout(_("joins the Romulans, wreaking terror on the Federation."))
2870     elif ifin == FSTRACTOR:
2871         prout(_("The shuttle craft Galileo is also caught,"))
2872         prout(_("and breaks up under the strain."))
2873         skip(1)
2874         prout(_("Your debris is scattered for millions of miles."))
2875         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
2876     elif ifin == FDRAY:
2877         prout(_("The mutants attack and kill Spock."))
2878         prout(_("Your ship is captured by Klingons, and"))
2879         prout(_("your crew is put on display in a Klingon zoo."))
2880     elif ifin == FTRIBBLE:
2881         prout(_("Tribbles consume all remaining water,"))
2882         prout(_("food, and oxygen on your ship."))
2883         skip(1)
2884         prout(_("You die of thirst, starvation, and asphyxiation."))
2885         prout(_("Your starship is a derelict in space."))
2886     elif ifin == FHOLE:
2887         prout(_("Your ship is drawn to the center of the black hole."))
2888         prout(_("You are crushed into extremely dense matter."))
2889     elif ifin == FCREW:
2890         prout(_("Your last crew member has died."))
2891     if game.ship == 'F':
2892         game.ship = None
2893     elif game.ship == 'E':
2894         game.ship = 'F'
2895     game.alive = False
2896     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0:
2897         goodies = game.state.remres/game.inresor
2898         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
2899         if goodies/baddies >= randreal(1.0, 1.5):
2900             prout(_("As a result of your actions, a treaty with the Klingon"))
2901             prout(_("Empire has been signed. The terms of the treaty are"))
2902             if goodies/baddies >= randreal(3.0):
2903                 prout(_("favorable to the Federation."))
2904                 skip(1)
2905                 prout(_("Congratulations!"))
2906             else:
2907                 prout(_("highly unfavorable to the Federation."))
2908         else:
2909             prout(_("The Federation will be destroyed."))
2910     else:
2911         prout(_("Since you took the last Klingon with you, you are a"))
2912         prout(_("martyr and a hero. Someday maybe they'll erect a"))
2913         prout(_("statue in your memory. Rest in peace, and try not"))
2914         prout(_("to think about pigeons."))
2915         game.gamewon = True
2916     score()
2917
2918 def score():
2919     "Compute player's score."
2920     timused = game.state.date - game.indate
2921     iskill = game.skill
2922     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
2923         timused = 5.0
2924     perdate = killrate()
2925     ithperd = 500*perdate + 0.5
2926     iwon = 0
2927     if game.gamewon:
2928         iwon = 100*game.skill
2929     if game.ship == 'E': 
2930         klship = 0
2931     elif game.ship == 'F': 
2932         klship = 1
2933     else:
2934         klship = 2
2935     iscore = 10*(game.inkling - game.state.remkl) \
2936              + 50*(game.incom - len(game.state.kcmdr)) \
2937              + ithperd + iwon \
2938              + 20*(game.inrom - game.state.nromrem) \
2939              + 200*(game.inscom - game.state.nscrem) \
2940              - game.state.nromrem \
2941              - badpoints()
2942     if not game.alive:
2943         iscore -= 200
2944     skip(2)
2945     prout(_("Your score --"))
2946     if game.inrom - game.state.nromrem:
2947         prout(_("%6d Romulans destroyed                 %5d") %
2948               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
2949     if game.state.nromrem and game.gamewon:
2950         prout(_("%6d Romulans captured                  %5d") %
2951               (game.state.nromrem, game.state.nromrem))
2952     if game.inkling - game.state.remkl:
2953         prout(_("%6d ordinary Klingons destroyed        %5d") %
2954               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
2955     if game.incom - len(game.state.kcmdr):
2956         prout(_("%6d Klingon commanders destroyed       %5d") %
2957               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
2958     if game.inscom - game.state.nscrem:
2959         prout(_("%6d Super-Commander destroyed          %5d") %
2960               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
2961     if ithperd:
2962         prout(_("%6.2f Klingons per stardate              %5d") %
2963               (perdate, ithperd))
2964     if game.state.starkl:
2965         prout(_("%6d stars destroyed by your action     %5d") %
2966               (game.state.starkl, -5*game.state.starkl))
2967     if game.state.nplankl:
2968         prout(_("%6d planets destroyed by your action   %5d") %
2969               (game.state.nplankl, -10*game.state.nplankl))
2970     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
2971         prout(_("%6d inhabited planets destroyed by your action   %5d") %
2972               (game.state.nworldkl, -300*game.state.nworldkl))
2973     if game.state.basekl:
2974         prout(_("%6d bases destroyed by your action     %5d") %
2975               (game.state.basekl, -100*game.state.basekl))
2976     if game.nhelp:
2977         prout(_("%6d calls for help from starbase       %5d") %
2978               (game.nhelp, -45*game.nhelp))
2979     if game.casual:
2980         prout(_("%6d casualties incurred                %5d") %
2981               (game.casual, -game.casual))
2982     if game.abandoned:
2983         prout(_("%6d crew abandoned in space            %5d") %
2984               (game.abandoned, -3*game.abandoned))
2985     if klship:
2986         prout(_("%6d ship(s) lost or destroyed          %5d") %
2987               (klship, -100*klship))
2988     if not game.alive:
2989         prout(_("Penalty for getting yourself killed        -200"))
2990     if game.gamewon:
2991         proutn(_("Bonus for winning "))
2992         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
2993         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
2994         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
2995         elif game.skill ==  SKILL_EXPERT:       proutn(_("Expert game  "))
2996         elif game.skill ==  SKILL_EMERITUS:     proutn(_("Emeritus game"))
2997         prout("           %5d" % iwon)
2998     skip(1)
2999     prout(_("TOTAL SCORE                               %5d") % iscore)
3000
3001 def plaque():
3002     "Emit winner's commemmorative plaque." 
3003     skip(2)
3004     while True:
3005         proutn(_("File or device name for your plaque: "))
3006         winner = cgetline()
3007         try:
3008             fp = open(winner, "w")
3009             break
3010         except IOError:
3011             prout(_("Invalid name."))
3012
3013     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3014     winner = cgetline()
3015     # The 38 below must be 64 for 132-column paper 
3016     nskip = 38 - len(winner)/2
3017     fp.write("\n\n\n\n")
3018     # --------DRAW ENTERPRISE PICTURE. 
3019     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3020     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3021     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3022     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3023     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3024     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3025     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3026     fp.write("                                      EEE           E          E            E  E\n")
3027     fp.write("                                                       E         E          E  E\n")
3028     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3029     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3030     fp.write("                                                    :E    :                 EEEE       E\n")
3031     fp.write("                                                   .-E   -:-----                       E\n")
3032     fp.write("                                                    :E    :                            E\n")
3033     fp.write("                                                      EE  :                    EEEEEEEE\n")
3034     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3035     fp.write("\n\n\n")
3036     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3037     fp.write("\n\n\n\n")
3038     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3039     fp.write("\n")
3040     fp.write(_("                                                Starfleet Command bestows to you\n"))
3041     fp.write("\n")
3042     fp.write("%*s%s\n\n" % (nskip, "", winner))
3043     fp.write(_("                                                           the rank of\n\n"))
3044     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3045     fp.write("                                                          ")
3046     if game.skill ==  SKILL_EXPERT:
3047         fp.write(_(" Expert level\n\n"))
3048     elif game.skill == SKILL_EMERITUS:
3049         fp.write(_("Emeritus level\n\n"))
3050     else:
3051         fp.write(_(" Cheat level\n\n"))
3052     timestring = time.ctime()
3053     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3054                     (timestring+4, timestring+20, timestring+11))
3055     fp.write(_("                                                        Your score:  %d\n\n") % iscore)
3056     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % perdate)
3057     fp.close()
3058
3059 # Code from io.c begins here
3060
3061 rows = linecount = 0    # for paging 
3062 stdscr = None
3063 replayfp = None
3064 fullscreen_window = None
3065 srscan_window     = None
3066 report_window     = None
3067 status_window     = None
3068 lrscan_window     = None
3069 message_window    = None
3070 prompt_window     = None
3071 curwnd = None
3072
3073 def iostart():
3074     global stdscr, rows
3075     "for some recent versions of python2, the following enables UTF8"
3076     "for the older ones we probably need to set C locale, and the python3"
3077     "has no problems at all"
3078     if sys.version_info[0] < 3:
3079         import locale
3080         locale.setlocale(locale.LC_ALL, "")
3081     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3082     gettext.textdomain("sst")
3083     if not (game.options & OPTION_CURSES):
3084         ln_env = os.getenv("LINES")
3085         if ln_env:
3086             rows = ln_env
3087         else:
3088             rows = 25
3089     else:
3090         stdscr = curses.initscr()
3091         stdscr.keypad(True)
3092         curses.nonl()
3093         curses.cbreak()
3094         if game.options & OPTION_COLOR:
3095             curses.start_color();
3096             curses.use_default_colors()
3097             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1);
3098             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1);
3099             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1);
3100             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1);
3101             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1);
3102             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1);
3103             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1);
3104             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1);
3105         global fullscreen_window, srscan_window, report_window, status_window
3106         global lrscan_window, message_window, prompt_window
3107         (rows, columns)   = stdscr.getmaxyx()
3108         fullscreen_window = stdscr
3109         srscan_window     = curses.newwin(12, 25, 0,       0)
3110         report_window     = curses.newwin(11, 0,  1,       25)
3111         status_window     = curses.newwin(10, 0,  1,       39)
3112         lrscan_window     = curses.newwin(5,  0,  0,       64) 
3113         message_window    = curses.newwin(0,  0,  12,      0)
3114         prompt_window     = curses.newwin(1,  0,  rows-2,  0) 
3115         message_window.scrollok(True)
3116         setwnd(fullscreen_window)
3117
3118 def ioend():
3119     "Wrap up I/O."
3120     if game.options & OPTION_CURSES:
3121         stdscr.keypad(False)
3122         curses.echo()
3123         curses.nocbreak()
3124         curses.endwin()
3125
3126 def waitfor():
3127     "Wait for user action -- OK to do nothing if on a TTY"
3128     if game.options & OPTION_CURSES:
3129         stdscr.getch()
3130
3131 def announce():
3132     skip(1)
3133     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3134     skip(1)
3135
3136 def pause_game():
3137     if game.skill > SKILL_FAIR:
3138         prompt = _("[CONTINUE?]")
3139     else:
3140         prompt = _("[PRESS ENTER TO CONTINUE]")
3141
3142     if game.options & OPTION_CURSES:
3143         drawmaps(0)
3144         setwnd(prompt_window)
3145         prompt_window.clear()
3146         prompt_window.addstr(prompt)
3147         prompt_window.getstr()
3148         prompt_window.clear()
3149         prompt_window.refresh()
3150         setwnd(message_window)
3151     else:
3152         global linecount
3153         sys.stdout.write('\n')
3154         proutn(prompt)
3155         raw_input()
3156         sys.stdout.write('\n' * rows)
3157         linecount = 0
3158
3159 def skip(i):
3160     "Skip i lines.  Pause game if this would cause a scrolling event."
3161     for dummy in range(i):
3162         if game.options & OPTION_CURSES:
3163             (y, x) = curwnd.getyx()
3164             (my, mx) = curwnd.getmaxyx()
3165             if curwnd == message_window and y >= my - 2:
3166                 pause_game()
3167                 clrscr()
3168             else:
3169                 try:
3170                     curwnd.move(y+1, 0)
3171                 except curses.error:
3172                     pass
3173         else:
3174             global linecount
3175             linecount += 1
3176             if rows and linecount >= rows:
3177                 pause_game()
3178             else:
3179                 sys.stdout.write('\n')
3180
3181 def proutn(line):
3182     "Utter a line with no following line feed."
3183     if game.options & OPTION_CURSES:
3184         curwnd.addstr(line)
3185         curwnd.refresh()
3186     else:
3187         sys.stdout.write(line)
3188         sys.stdout.flush()
3189
3190 def prout(line):
3191     proutn(line)
3192     skip(1)
3193
3194 def prouts(line):
3195     "Emit slowly!" 
3196     for c in line:
3197         if not replayfp or replayfp.closed:     # Don't slow down replays
3198             time.sleep(0.03)
3199         proutn(c)
3200         if game.options & OPTION_CURSES:
3201             curwnd.refresh()
3202         else:
3203             sys.stdout.flush()
3204     if not replayfp or replayfp.closed:
3205         time.sleep(0.03)
3206
3207 def cgetline():
3208     "Get a line of input."
3209     if game.options & OPTION_CURSES:
3210         line = curwnd.getstr() + "\n"
3211         curwnd.refresh()
3212     else:
3213         if replayfp and not replayfp.closed:
3214             while True:
3215                 line = replayfp.readline()
3216                 proutn(line)
3217                 if line == '':
3218                     prout("*** Replay finished")
3219                     replayfp.close()
3220                     break
3221                 elif line[0] != "#":
3222                     break
3223         else:
3224             line = raw_input() + "\n"
3225     if logfp:
3226         logfp.write(line)
3227     return line
3228
3229 def setwnd(wnd):
3230     "Change windows -- OK for this to be a no-op in tty mode."
3231     global curwnd
3232     if game.options & OPTION_CURSES:
3233         curwnd = wnd
3234         curses.curs_set(wnd == fullscreen_window or wnd == message_window or wnd == prompt_window)
3235
3236 def clreol():
3237     "Clear to end of line -- can be a no-op in tty mode" 
3238     if game.options & OPTION_CURSES:
3239         curwnd.clrtoeol()
3240         curwnd.refresh()
3241
3242 def clrscr():
3243     "Clear screen -- can be a no-op in tty mode."
3244     global linecount
3245     if game.options & OPTION_CURSES:
3246        curwnd.clear()
3247        curwnd.move(0, 0)
3248        curwnd.refresh()
3249     linecount = 0
3250
3251 def textcolor(color=DEFAULT):
3252     if game.options & OPTION_COLOR:
3253         if color == DEFAULT: 
3254             curwnd.attrset(0);
3255         elif color ==  BLACK: 
3256             curwnd.attron(curses.color_pair(curses.COLOR_BLACK));
3257         elif color ==  BLUE: 
3258             curwnd.attron(curses.color_pair(curses.COLOR_BLUE));
3259         elif color ==  GREEN: 
3260             curwnd.attron(curses.color_pair(curses.COLOR_GREEN));
3261         elif color ==  CYAN: 
3262             curwnd.attron(curses.color_pair(curses.COLOR_CYAN));
3263         elif color ==  RED: 
3264             curwnd.attron(curses.color_pair(curses.COLOR_RED));
3265         elif color ==  MAGENTA: 
3266             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA));
3267         elif color ==  BROWN: 
3268             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW));
3269         elif color ==  LIGHTGRAY: 
3270             curwnd.attron(curses.color_pair(curses.COLOR_WHITE));
3271         elif color ==  DARKGRAY: 
3272             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD);
3273         elif color ==  LIGHTBLUE: 
3274             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD);
3275         elif color ==  LIGHTGREEN: 
3276             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD);
3277         elif color ==  LIGHTCYAN: 
3278             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD);
3279         elif color ==  LIGHTRED: 
3280             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD);
3281         elif color ==  LIGHTMAGENTA: 
3282             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD);
3283         elif color ==  YELLOW: 
3284             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD);
3285         elif color ==  WHITE:
3286             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD);
3287
3288 def highvideo():
3289     if game.options & OPTION_COLOR:
3290         curwnd.attron(curses.A_REVERSE)
3291
3292 #
3293 # Things past this point have policy implications.
3294
3295
3296 def drawmaps(mode):
3297     "Hook to be called after moving to redraw maps."
3298     if game.options & OPTION_CURSES:
3299         if mode == 1:
3300             sensor()
3301         setwnd(srscan_window)
3302         curwnd.move(0, 0)
3303         srscan()
3304         if mode != 2:
3305             setwnd(status_window)
3306             status_window.clear()
3307             status_window.move(0, 0)
3308             setwnd(report_window)
3309             report_window.clear()
3310             report_window.move(0, 0)
3311             status()
3312             setwnd(lrscan_window)
3313             lrscan_window.clear()
3314             lrscan_window.move(0, 0)
3315             lrscan(silent=False)
3316
3317 def put_srscan_sym(w, sym):
3318     "Emit symbol for short-range scan."
3319     srscan_window.move(w.i+1, w.j*2+2)
3320     srscan_window.addch(sym)
3321     srscan_window.refresh()
3322
3323 def boom(w):
3324     "Enemy fall down, go boom."  
3325     if game.options & OPTION_CURSES:
3326         drawmaps(2)
3327         setwnd(srscan_window)
3328         srscan_window.attron(curses.A_REVERSE)
3329         put_srscan_sym(w, game.quad[w.i][w.j])
3330         #sound(500)
3331         #time.sleep(1.0)
3332         #nosound()
3333         srscan_window.attroff(curses.A_REVERSE)
3334         put_srscan_sym(w, game.quad[w.i][w.j])
3335         curses.delay_output(500)
3336         setwnd(message_window) 
3337
3338 def warble():
3339     "Sound and visual effects for teleportation."
3340     if game.options & OPTION_CURSES:
3341         drawmaps(2)
3342         setwnd(message_window)
3343         #sound(50)
3344     prouts("     . . . . .     ")
3345     if game.options & OPTION_CURSES:
3346         #curses.delay_output(1000)
3347         #nosound()
3348         pass
3349
3350 def tracktorpedo(origin, w, step, i, n, iquad):
3351     "Torpedo-track animation." 
3352     if not game.options & OPTION_CURSES:
3353         if step == 1:
3354             if n != 1:
3355                 skip(1)
3356                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3357             else:
3358                 skip(1)
3359                 proutn(_("Torpedo track- "))
3360         elif step==4 or step==9: 
3361             skip(1)
3362         proutn("%s   " % w)
3363     else:
3364         if not damaged(DSRSENS) or game.condition=="docked":
3365             if i != 0 and step == 1:
3366                 drawmaps(2)
3367                 time.sleep(0.4)
3368             if (iquad=='.') or (iquad==' '):
3369                 put_srscan_sym(w, '+')
3370                 #sound(step*10)
3371                 #time.sleep(0.1)
3372                 #nosound()
3373                 put_srscan_sym(w, iquad)
3374             else:
3375                 curwnd.attron(curses.A_REVERSE)
3376                 put_srscan_sym(w, iquad)
3377                 #sound(500)
3378                 #time.sleep(1.0)
3379                 #nosound()
3380                 curwnd.attroff(curses.A_REVERSE)
3381                 put_srscan_sym(w, iquad)
3382         else:
3383             proutn("%s   " % w)
3384
3385 def makechart():
3386     "Display the current galaxy chart."
3387     if game.options & OPTION_CURSES:
3388         setwnd(message_window)
3389         message_window.clear()
3390     chart()
3391     if game.options & OPTION_TTY:
3392         skip(1)
3393
3394 NSYM    = 14
3395
3396 def prstat(txt, data):
3397     proutn(txt)
3398     if game.options & OPTION_CURSES:
3399         skip(1)
3400         setwnd(status_window)
3401     else:
3402         proutn(" " * (NSYM - len(txt)))
3403     proutn(data)
3404     skip(1)
3405     if game.options & OPTION_CURSES:
3406         setwnd(report_window)
3407
3408 # Code from moving.c begins here
3409
3410 def imove(icourse=None, noattack=False):
3411     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3412     w = Coord()
3413
3414     def newquadrant(noattack):
3415         # Leaving quadrant -- allow final enemy attack 
3416         # Don't do it if being pushed by Nova 
3417         if len(game.enemies) != 0 and not noattack:
3418             newcnd()
3419             for enemy in game.enemies:
3420                 finald = (w - enemy.location).distance()
3421                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3422             # Stas Sergeev added the condition
3423             # that attacks only happen if Klingons
3424             # are present and your skill is good.
3425             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3426                 attack(torps_ok=False)
3427             if game.alldone:
3428                 return
3429         # check for edge of galaxy 
3430         kinks = 0
3431         while True:
3432             kink = False
3433             if icourse.final.i < 0:
3434                 icourse.final.i = -icourse.final.i
3435                 kink = True
3436             if icourse.final.j < 0:
3437                 icourse.final.j = -icourse.final.j
3438                 kink = True
3439             if icourse.final.i >= GALSIZE*QUADSIZE:
3440                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3441                 kink = True
3442             if icourse.final.j >= GALSIZE*QUADSIZE:
3443                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3444                 kink = True
3445             if kink:
3446                 kinks += 1
3447             else:
3448                 break
3449         if kinks:
3450             game.nkinks += 1
3451             if game.nkinks == 3:
3452                 # Three strikes -- you're out! 
3453                 finish(FNEG3)
3454                 return
3455             skip(1)
3456             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3457             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3458             prout(_("YOU WILL BE DESTROYED."))
3459         # Compute final position in new quadrant 
3460         if trbeam: # Don't bother if we are to be beamed 
3461             return
3462         game.quadrant = icourse.final.quadrant()
3463         game.sector = icourse.final.sector()
3464         skip(1)
3465         prout(_("Entering Quadrant %s.") % game.quadrant)
3466         game.quad[game.sector.i][game.sector.j] = game.ship
3467         newqad()
3468         if game.skill>SKILL_NOVICE:
3469             attack(torps_ok=False)  
3470
3471     def check_collision(h):
3472         iquad = game.quad[h.i][h.j]
3473         if iquad != '.':
3474             # object encountered in flight path 
3475             stopegy = 50.0*icourse.distance/game.optime
3476             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3477                 for enemy in game.enemies:
3478                     if enemy.location == game.sector:
3479                         break
3480                 collision(rammed=False, enemy=enemy)
3481                 return True
3482             elif iquad == ' ':
3483                 skip(1)
3484                 prouts(_("***RED ALERT!  RED ALERT!"))
3485                 skip(1)
3486                 proutn("***" + crmshp())
3487                 proutn(_(" pulled into black hole at Sector %s") % h)
3488                 # Getting pulled into a black hole was certain
3489                 # death in Almy's original.  Stas Sergeev added a
3490                 # possibility that you'll get timewarped instead.
3491                 n=0
3492                 for m in range(NDEVICES):
3493                     if game.damage[m]>0: 
3494                         n += 1
3495                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3496                 if (game.options & OPTION_BLKHOLE) and withprob(1-probf): 
3497                     timwrp()
3498                 else: 
3499                     finish(FHOLE)
3500                 return True
3501             else:
3502                 # something else 
3503                 skip(1)
3504                 proutn(crmshp())
3505                 if iquad == '#':
3506                     prout(_(" encounters Tholian web at %s;") % h)
3507                 else:
3508                     prout(_(" blocked by object at %s;") % h)
3509                 proutn(_("Emergency stop required "))
3510                 prout(_("%2d units of energy.") % int(stopegy))
3511                 game.energy -= stopegy
3512                 if game.energy <= 0:
3513                     finish(FNRG)
3514                 return True
3515         return False
3516
3517     trbeam = False
3518     if game.inorbit:
3519         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3520         game.inorbit = False
3521     # If tractor beam is to occur, don't move full distance 
3522     if game.state.date+game.optime >= scheduled(FTBEAM):
3523         trbeam = True
3524         game.condition = "red"
3525         icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3526         game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3527     # Move out
3528     game.quad[game.sector.i][game.sector.j] = '.'
3529     for m in range(icourse.moves):
3530         icourse.next()
3531         w = icourse.sector()
3532         if icourse.origin.quadrant() != icourse.location.quadrant():
3533             newquadrant(noattack)
3534             break
3535         elif check_collision(icourse, w):
3536             print "Collision detected"
3537             break
3538         else:
3539             game.sector = w
3540     # We're in destination quadrant -- compute new average enemy distances
3541     game.quad[game.sector.i][game.sector.j] = game.ship
3542     if game.enemies:
3543         for enemy in game.enemies:
3544             finald = (w-enemy.location).distance()
3545             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3546             enemy.kdist = finald
3547         game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
3548         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3549             attack(torps_ok=False)
3550         for enemy in game.enemies:
3551             enemy.kavgd = enemy.kdist
3552     newcnd()
3553     drawmaps(0)
3554     setwnd(message_window)
3555     return
3556
3557 def dock(verbose):
3558     "Dock our ship at a starbase."
3559     scanner.chew()
3560     if game.condition == "docked" and verbose:
3561         prout(_("Already docked."))
3562         return
3563     if game.inorbit:
3564         prout(_("You must first leave standard orbit."))
3565         return
3566     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3567         prout(crmshp() + _(" not adjacent to base."))
3568         return
3569     game.condition = "docked"
3570     if "verbose":
3571         prout(_("Docked."))
3572     game.ididit = True
3573     if game.energy < game.inenrg:
3574         game.energy = game.inenrg
3575     game.shield = game.inshld
3576     game.torps = game.intorps
3577     game.lsupres = game.inlsr
3578     game.state.crew = FULLCREW
3579     if not damaged(DRADIO) and \
3580         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3581         # get attack report from base 
3582         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3583         attackreport(False)
3584         game.iseenit = True
3585
3586 def cartesian(loc1=None, loc2=None):
3587     if loc1 is None:
3588         return game.quadrant * QUADSIZE + game.sector
3589     elif loc2 is None:
3590         return game.quadrant * QUADSIZE + loc1
3591     else:
3592         return loc1 * QUADSIZE + loc2
3593
3594 def getcourse(isprobe):
3595     "Get a course and distance from the user."
3596     key = 0
3597     dquad = copy.copy(game.quadrant)
3598     navmode = "unspecified"
3599     itemp = "curt"
3600     dsect = Coord()
3601     iprompt = False
3602     if game.landed and not isprobe:
3603         prout(_("Dummy! You can't leave standard orbit until you"))
3604         proutn(_("are back aboard the ship."))
3605         scanner.chew()
3606         raise TrekError
3607     while navmode == "unspecified":
3608         if damaged(DNAVSYS):
3609             if isprobe:
3610                 prout(_("Computer damaged; manual navigation only"))
3611             else:
3612                 prout(_("Computer damaged; manual movement only"))
3613             scanner.chew()
3614             navmode = "manual"
3615             key = "IHEOL"
3616             break
3617         key = scanner.next()
3618         if key == "IHEOL":
3619             proutn(_("Manual or automatic- "))
3620             iprompt = True
3621             scanner.chew()
3622         elif key == "IHALPHA":
3623             if scanner.sees("manual"):
3624                 navmode = "manual"
3625                 key = scanner.next()
3626                 break
3627             elif scanner.sees("automatic"):
3628                 navmode = "automatic"
3629                 key = scanner.next()
3630                 break
3631             else:
3632                 huh()
3633                 scanner.chew()
3634                 raise TrekError
3635         else: # numeric 
3636             if isprobe:
3637                 prout(_("(Manual navigation assumed.)"))
3638             else:
3639                 prout(_("(Manual movement assumed.)"))
3640             navmode = "manual"
3641             break
3642     delta = Coord()
3643     if navmode == "automatic":
3644         while key == "IHEOL":
3645             if isprobe:
3646                 proutn(_("Target quadrant or quadrant&sector- "))
3647             else:
3648                 proutn(_("Destination sector or quadrant&sector- "))
3649             scanner.chew()
3650             iprompt = True
3651             key = scanner.next()
3652         if key != "IHREAL":
3653             huh()
3654             raise TrekError
3655         xi = int(round(scanner.real))-1
3656         key = scanner.next()
3657         if key != "IHREAL":
3658             huh()
3659             raise TrekError
3660         xj = int(round(scanner.real))-1
3661         key = scanner.next()
3662         if key == "IHREAL":
3663             # both quadrant and sector specified 
3664             xk = int(round(scanner.real))-1
3665             key = scanner.next()
3666             if key != "IHREAL":
3667                 huh()
3668                 raise TrekError
3669             xl = int(round(scanner.real))-1
3670             dquad.i = xi
3671             dquad.j = xj
3672             dsect.i = xk
3673             dsect.j = xl
3674         else:
3675             # only one pair of numbers was specified
3676             if isprobe:
3677                 # only quadrant specified -- go to center of dest quad 
3678                 dquad.i = xi
3679                 dquad.j = xj
3680                 dsect.j = dsect.i = 4   # preserves 1-origin behavior
3681             else:
3682                 # only sector specified
3683                 dsect.i = xi
3684                 dsect.j = xj
3685             itemp = "normal"
3686         if not dquad.valid_quadrant() or not dsect.valid_sector():
3687             huh()
3688             raise TrekError
3689         skip(1)
3690         if not isprobe:
3691             if itemp > "curt":
3692                 if iprompt:
3693                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
3694             else:
3695                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
3696         # the actual deltas get computed here
3697         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
3698         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
3699     else: # manual 
3700         while key == "IHEOL":
3701             proutn(_("X and Y displacements- "))
3702             scanner.chew()
3703             iprompt = True
3704             key = scanner.next()
3705         itemp = "verbose"
3706         if key != "IHREAL":
3707             huh()
3708             raise TrekError
3709         delta.j = scanner.real
3710         key = scanner.next()
3711         if key != "IHREAL":
3712             huh()
3713             raise TrekError
3714         delta.i = scanner.real
3715     # Check for zero movement 
3716     if delta.i == 0 and delta.j == 0:
3717         scanner.chew()
3718         raise TrekError
3719     if itemp == "verbose" and not isprobe:
3720         skip(1)
3721         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
3722     scanner.chew()
3723     return course(bearing=delta.bearing(), distance=delta.distance())
3724
3725 class course:
3726     def __init__(self, bearing, distance, origin=None): 
3727         self.distance = distance
3728         self.bearing = bearing
3729         if origin is None:
3730             self.origin = cartesian(game.quadrant, game.sector)
3731         else:
3732             self.origin = origin
3733         # The bearing() code we inherited from FORTRAN is actually computing
3734         # clockface directions!
3735         if self.bearing < 0.0:
3736             self.bearing += 12.0
3737         self.angle = ((15.0 - self.bearing) * 0.5235988)
3738         if origin is None:
3739             self.origin = cartesian(game.quadrant, game.sector)
3740         else:
3741             self.origin = cartesian(game.quadrant, origin)
3742         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
3743         bigger = max(abs(self.increment.i), abs(self.increment.j))
3744         self.increment /= bigger
3745         self.moves = int(round(10*self.distance*bigger))
3746         self.reset()
3747         self.final = (self.location + self.moves*self.increment).roundtogrid()
3748     def reset(self):
3749         self.location = self.origin
3750         self.step = 0
3751     def arrived(self):
3752         return self.location.roundtogrid() == self.final
3753     def next(self):
3754         "Next step on course."
3755         self.step += 1
3756         self.nextlocation = self.location + self.increment
3757         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
3758         self.location = self.nextlocation
3759         return samequad
3760     def quadrant(self):
3761         return self.location.quadrant()
3762     def sector(self):
3763         return self.location.sector()
3764     def power(self, warp):
3765         return self.distance*(warp**3)*(game.shldup+1)
3766     def time(self, warp):
3767         return 10.0*self.distance/warp**2
3768
3769 def impulse():
3770     "Move under impulse power."
3771     game.ididit = False
3772     if damaged(DIMPULS):
3773         scanner.chew()
3774         skip(1)
3775         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
3776         return
3777     if game.energy > 30.0:
3778         try:
3779             course = getcourse(isprobe=False)
3780         except TrekError:
3781             return
3782         power = 20.0 + 100.0*course.distance
3783     else:
3784         power = 30.0
3785     if power >= game.energy:
3786         # Insufficient power for trip 
3787         skip(1)
3788         prout(_("First Officer Spock- \"Captain, the impulse engines"))
3789         prout(_("require 20.0 units to engage, plus 100.0 units per"))
3790         if game.energy > 30:
3791             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
3792                      int(0.01 * (game.energy-20.0)-0.05))
3793             prout(_(" quadrants.\""))
3794         else:
3795             prout(_("quadrant.  They are, therefore, useless.\""))
3796         scanner.chew()
3797         return
3798     # Make sure enough time is left for the trip 
3799     game.optime = course.dist/0.095
3800     if game.optime >= game.state.remtime:
3801         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
3802         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
3803         proutn(_("we dare spend the time?\" "))
3804         if not ja():
3805             return
3806     # Activate impulse engines and pay the cost 
3807     imove(course, noattack=False)
3808     game.ididit = True
3809     if game.alldone:
3810         return
3811     power = 20.0 + 100.0*course.dist
3812     game.energy -= power
3813     game.optime = course.dist/0.095
3814     if game.energy <= 0:
3815         finish(FNRG)
3816     return
3817
3818 def warp(wcourse, involuntary):
3819     "ove under warp drive."
3820     blooey = False; twarp = False
3821     if not involuntary: # Not WARPX entry 
3822         game.ididit = False
3823         if game.damage[DWARPEN] > 10.0:
3824             scanner.chew()
3825             skip(1)
3826             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
3827             return
3828         if damaged(DWARPEN) and game.warpfac > 4.0:
3829             scanner.chew()
3830             skip(1)
3831             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
3832             prout(_("  is repaired, I can only give you warp 4.\""))
3833             return
3834         # Read in course and distance
3835         if wcourse==None:
3836             try:
3837                 wcourse = getcourse(isprobe=False)
3838             except TrekError:
3839                 return
3840         # Make sure starship has enough energy for the trip
3841         # Note: this formula is slightly different from the C version,
3842         # and lets you skate a bit closer to the edge.
3843         if wcourse.power(game.warpfac) >= game.energy:
3844             # Insufficient power for trip 
3845             game.ididit = False
3846             skip(1)
3847             prout(_("Engineering to bridge--"))
3848             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
3849                 iwarp = (game.energy/(wcourse.dist+0.05)) ** 0.333333333
3850                 if iwarp <= 0:
3851                     prout(_("We can't do it, Captain. We don't have enough energy."))
3852                 else:
3853                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
3854                     if game.shldup:
3855                         prout(",")
3856                         prout(_("if you'll lower the shields."))
3857                     else:
3858                         prout(".")
3859             else:
3860                 prout(_("We haven't the energy to go that far with the shields up."))
3861             return                              
3862         # Make sure enough time is left for the trip 
3863         game.optime = wcourse.time(game.warpfac)
3864         if game.optime >= 0.8*game.state.remtime:
3865             skip(1)
3866             prout(_("First Officer Spock- \"Captain, I compute that such"))
3867             proutn(_("  a trip would require approximately %2.0f") %
3868                    (100.0*game.optime/game.state.remtime))
3869             prout(_(" percent of our"))
3870             proutn(_("  remaining time.  Are you sure this is wise?\" "))
3871             if not ja():
3872                 game.ididit = False
3873                 game.optime=0 
3874                 return
3875     # Entry WARPX 
3876     if game.warpfac > 6.0:
3877         # Decide if engine damage will occur
3878         # ESR: Seems wrong. Probability of damage goes *down* with distance? 
3879         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
3880         if prob > randreal():
3881             blooey = True
3882             wcourse.distance = randreal(wcourse.distance)
3883         # Decide if time warp will occur 
3884         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
3885             twarp = True
3886         if game.idebug and game.warpfac==10 and not twarp:
3887             blooey = False
3888             proutn("=== Force time warp? ")
3889             if ja():
3890                 twarp = True
3891         if blooey or twarp:
3892             # If time warp or engine damage, check path 
3893             # If it is obstructed, don't do warp or damage 
3894             for m_unused in range(wcourse.moves):
3895                 wcourse.next()
3896                 w = wcourse.sector()
3897                 if not w.valid_sector():
3898                     break
3899                 if game.quad[w.i][w.j] != '.':
3900                     blooey = False
3901                     twarp = False
3902             wcourse.reset()
3903     # Activate Warp Engines and pay the cost 
3904     imove(course, noattack=False)
3905     if game.alldone:
3906         return
3907     game.energy -= wcourse.power(game.warpfac)
3908     if game.energy <= 0:
3909         finish(FNRG)
3910     game.optime = wcourse.time(game.warpfac)
3911     if twarp:
3912         timwrp()
3913     if blooey:
3914         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
3915         skip(1)
3916         prout(_("Engineering to bridge--"))
3917         prout(_("  Scott here.  The warp engines are damaged."))
3918         prout(_("  We'll have to reduce speed to warp 4."))
3919     game.ididit = True
3920     return
3921
3922 def setwarp():
3923     "Change the warp factor."
3924     while True:
3925         key=scanner.next()
3926         if key != "IHEOL":
3927             break
3928         scanner.chew()
3929         proutn(_("Warp factor- "))
3930     if key != "IHREAL":
3931         huh()
3932         return
3933     if game.damage[DWARPEN] > 10.0:
3934         prout(_("Warp engines inoperative."))
3935         return
3936     if damaged(DWARPEN) and scanner.real > 4.0:
3937         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
3938         prout(_("  but right now we can only go warp 4.\""))
3939         return
3940     if scanner.real > 10.0:
3941         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
3942         return
3943     if scanner.real < 1.0:
3944         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
3945         return
3946     oldfac = game.warpfac
3947     game.warpfac = scanner.real
3948     if game.warpfac <= oldfac or game.warpfac <= 6.0:
3949         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
3950                int(game.warpfac))
3951         return
3952     if game.warpfac < 8.00:
3953         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
3954         return
3955     if game.warpfac == 10.0:
3956         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
3957         return
3958     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
3959     return
3960
3961 def atover(igrab):
3962     "Cope with being tossed out of quadrant by supernova or yanked by beam."
3963     scanner.chew()
3964     # is captain on planet? 
3965     if game.landed:
3966         if damaged(DTRANSP):
3967             finish(FPNOVA)
3968             return
3969         prout(_("Scotty rushes to the transporter controls."))
3970         if game.shldup:
3971             prout(_("But with the shields up it's hopeless."))
3972             finish(FPNOVA)
3973         prouts(_("His desperate attempt to rescue you . . ."))
3974         if withprob(0.5):
3975             prout(_("fails."))
3976             finish(FPNOVA)
3977             return
3978         prout(_("SUCCEEDS!"))
3979         if game.imine:
3980             game.imine = False
3981             proutn(_("The crystals mined were "))
3982             if withprob(0.25):
3983                 prout(_("lost."))
3984             else:
3985                 prout(_("saved."))
3986                 game.icrystl = True
3987     if igrab:
3988         return
3989     # Check to see if captain in shuttle craft 
3990     if game.icraft:
3991         finish(FSTRACTOR)
3992     if game.alldone:
3993         return
3994     # Inform captain of attempt to reach safety 
3995     skip(1)
3996     while True:
3997         if game.justin:
3998             prouts(_("***RED ALERT!  RED ALERT!"))
3999             skip(1)
4000             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4001             prouts(_("   a supernova."))
4002             skip(2)
4003         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4004         prout(_("safely out of quadrant."))
4005         if not damaged(DRADIO):
4006             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4007         # Try to use warp engines 
4008         if damaged(DWARPEN):
4009             skip(1)
4010             prout(_("Warp engines damaged."))
4011             finish(FSNOVAED)
4012             return
4013         game.warpfac = randreal(6.0, 8.0)
4014         prout(_("Warp factor set to %d") % int(game.warpfac))
4015         power = 0.75*game.energy
4016         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4017         dist = max(dist, randreal(math.sqrt(2)))
4018         bugout = course(bearing=randreal(12), distance=dist)    # How dumb!
4019         game.optime = bugout.time(game.warpfac)
4020         game.justin = False
4021         game.inorbit = False
4022         warp(bugout, involuntary=True)
4023         if not game.justin:
4024             # This is bad news, we didn't leave quadrant. 
4025             if game.alldone:
4026                 return
4027             skip(1)
4028             prout(_("Insufficient energy to leave quadrant."))
4029             finish(FSNOVAED)
4030             return
4031         # Repeat if another snova
4032         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4033             break
4034     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0: 
4035         finish(FWON) # Snova killed remaining enemy. 
4036
4037 def timwrp():
4038     "Let's do the time warp again."
4039     prout(_("***TIME WARP ENTERED."))
4040     if game.state.snap and withprob(0.5):
4041         # Go back in time 
4042         prout(_("You are traveling backwards in time %d stardates.") %
4043               int(game.state.date-game.snapsht.date))
4044         game.state = game.snapsht
4045         game.state.snap = False
4046         if len(game.state.kcmdr):
4047             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4048             schedule(FBATTAK, expran(0.3*game.intime))
4049         schedule(FSNOVA, expran(0.5*game.intime))
4050         # next snapshot will be sooner 
4051         schedule(FSNAP, expran(0.25*game.state.remtime))
4052                                 
4053         if game.state.nscrem:
4054             schedule(FSCMOVE, 0.2777)       
4055         game.isatb = 0
4056         unschedule(FCDBAS)
4057         unschedule(FSCDBAS)
4058         game.battle.invalidate()
4059         # Make sure Galileo is consistant -- Snapshot may have been taken
4060         # when on planet, which would give us two Galileos! 
4061         gotit = False
4062         for l in range(game.inplan):
4063             if game.state.planets[l].known == "shuttle_down":
4064                 gotit = True
4065                 if game.iscraft == "onship" and game.ship=='E':
4066                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4067                     game.iscraft = "offship"
4068         # Likewise, if in the original time the Galileo was abandoned, but
4069         # was on ship earlier, it would have vanished -- let's restore it.
4070         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4071             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4072             game.iscraft = "onship"
4073         # There used to be code to do the actual reconstrction here,
4074         # but the starchart is now part of the snapshotted galaxy state.
4075         prout(_("Spock has reconstructed a correct star chart from memory"))
4076     else:
4077         # Go forward in time 
4078         game.optime = expran(0.5*game.intime)
4079         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4080         # cheat to make sure no tractor beams occur during time warp 
4081         postpone(FTBEAM, game.optime)
4082         game.damage[DRADIO] += game.optime
4083     newqad()
4084     events()    # Stas Sergeev added this -- do pending events 
4085
4086 def probe():
4087     "Launch deep-space probe." 
4088     # New code to launch a deep space probe 
4089     if game.nprobes == 0:
4090         scanner.chew()
4091         skip(1)
4092         if game.ship == 'E': 
4093             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4094         else:
4095             prout(_("Ye Faerie Queene has no deep space probes."))
4096         return
4097     if damaged(DDSP):
4098         scanner.chew()
4099         skip(1)
4100         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4101         return
4102     if is_scheduled(FDSPROB):
4103         scanner.chew()
4104         skip(1)
4105         if damaged(DRADIO) and game.condition != "docked":
4106             prout(_("Spock-  \"Records show the previous probe has not yet"))
4107             prout(_("   reached its destination.\""))
4108         else:
4109             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4110         return
4111     key = scanner.next()
4112     if key == "IHEOL":
4113         if game.nprobes == 1:
4114             prout(_("1 probe left."))
4115         else:
4116             prout(_("%d probes left") % game.nprobes)
4117         proutn(_("Are you sure you want to fire a probe? "))
4118         if not ja():
4119             return
4120     game.isarmed = False
4121     if key == "IHALPHA" and scanner.token == "armed":
4122         game.isarmed = True
4123         key = scanner.next()
4124     elif key == "IHEOL":
4125         proutn(_("Arm NOVAMAX warhead? "))
4126         game.isarmed = ja()
4127     elif key == "IHREAL":               # first element of course
4128         scanner.push(scanner.token)
4129     try:
4130         game.probe = getcourse(isprobe=True)
4131     except TrekError:
4132         return
4133     game.nprobes -= 1
4134     schedule(FDSPROB, 0.01) # Time to move one sector
4135     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4136     game.ididit = True
4137     return
4138
4139 def mayday():
4140     "Yell for help from nearest starbase."
4141     # There's more than one way to move in this game! 
4142     scanner.chew()
4143     # Test for conditions which prevent calling for help 
4144     if game.condition == "docked":
4145         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4146         return
4147     if damaged(DRADIO):
4148         prout(_("Subspace radio damaged."))
4149         return
4150     if not game.state.baseq:
4151         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4152         return
4153     if game.landed:
4154         prout(_("You must be aboard the %s.") % crmshp())
4155         return
4156     # OK -- call for help from nearest starbase 
4157     game.nhelp += 1
4158     if game.base.i!=0:
4159         # There's one in this quadrant 
4160         ddist = (game.base - game.sector).distance()
4161     else:
4162         ddist = FOREVER
4163         for ibq in game.state.baseq:
4164             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4165             if xdist < ddist:
4166                 ddist = xdist
4167         # Since starbase not in quadrant, set up new quadrant 
4168         game.quadrant = ibq
4169         newqad()
4170     # dematerialize starship 
4171     game.quad[game.sector.i][game.sector.j]='.'
4172     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4173            % (game.quadrant, crmshp()))
4174     game.sector.invalidate()
4175     for m in range(1, 5+1):
4176         w = game.base.scatter() 
4177         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4178             # found one -- finish up 
4179             game.sector = w
4180             break
4181     if not game.sector.is_valid():
4182         prout(_("You have been lost in space..."))
4183         finish(FMATERIALIZE)
4184         return
4185     # Give starbase three chances to rematerialize starship 
4186     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4187     for m in range(1, 3+1):
4188         if m == 1: proutn(_("1st"))
4189         elif m == 2: proutn(_("2nd"))
4190         elif m == 3: proutn(_("3rd"))
4191         proutn(_(" attempt to re-materialize ") + crmshp())
4192         game.quad[ix][iy]=('-','o','O')[m-1]
4193         textcolor(RED)
4194         warble()
4195         if randreal() > probf:
4196             break
4197         prout(_("fails."))
4198         textcolor(DEFAULT)
4199         curses.delay_output(500)
4200     if m > 3:
4201         game.quad[ix][iy]='?'
4202         game.alive = False
4203         drawmaps(1)
4204         setwnd(message_window)
4205         finish(FMATERIALIZE)
4206         return
4207     game.quad[ix][iy]=game.ship
4208     textcolor(GREEN);
4209     prout(_("succeeds."))
4210     textcolor(DEFAULT);
4211     dock(False)
4212     skip(1)
4213     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4214
4215 def abandon():
4216     "Abandon ship."
4217     scanner.chew()
4218     if game.condition=="docked":
4219         if game.ship!='E':
4220             prout(_("You cannot abandon Ye Faerie Queene."))
4221             return
4222     else:
4223         # Must take shuttle craft to exit 
4224         if game.damage[DSHUTTL]==-1:
4225             prout(_("Ye Faerie Queene has no shuttle craft."))
4226             return
4227         if game.damage[DSHUTTL]<0:
4228             prout(_("Shuttle craft now serving Big Macs."))
4229             return
4230         if game.damage[DSHUTTL]>0:
4231             prout(_("Shuttle craft damaged."))
4232             return
4233         if game.landed:
4234             prout(_("You must be aboard the ship."))
4235             return
4236         if game.iscraft != "onship":
4237             prout(_("Shuttle craft not currently available."))
4238             return
4239         # Emit abandon ship messages 
4240         skip(1)
4241         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4242         skip(1)
4243         prouts(_("***ALL HANDS ABANDON SHIP!"))
4244         skip(2)
4245         prout(_("Captain and crew escape in shuttle craft."))
4246         if not game.state.baseq:
4247             # Oops! no place to go... 
4248             finish(FABANDN)
4249             return
4250         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4251         # Dispose of crew 
4252         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4253             prout(_("Remainder of ship's complement beam down"))
4254             prout(_("to nearest habitable planet."))
4255         elif q.planet != None and not damaged(DTRANSP):
4256             prout(_("Remainder of ship's complement beam down to %s.") %
4257                     q.planet)
4258         else:
4259             prout(_("Entire crew of %d left to die in outer space.") %
4260                     game.state.crew)
4261             game.casual += game.state.crew
4262             game.abandoned += game.state.crew
4263         # If at least one base left, give 'em the Faerie Queene 
4264         skip(1)
4265         game.icrystl = False # crystals are lost 
4266         game.nprobes = 0 # No probes 
4267         prout(_("You are captured by Klingons and released to"))
4268         prout(_("the Federation in a prisoner-of-war exchange."))
4269         nb = randrange(len(game.state.baseq))
4270         # Set up quadrant and position FQ adjacient to base 
4271         if not game.quadrant == game.state.baseq[nb]:
4272             game.quadrant = game.state.baseq[nb]
4273             game.sector.i = game.sector.j = 5
4274             newqad()
4275         while True:
4276             # position next to base by trial and error 
4277             game.quad[game.sector.i][game.sector.j] = '.'
4278             for l in range(QUADSIZE):
4279                 game.sector = game.base.scatter()
4280                 if game.sector.valid_sector() and \
4281                        game.quad[game.sector.i][game.sector.j] == '.':
4282                     break
4283             if l < QUADSIZE+1:
4284                 break # found a spot 
4285             game.sector.i=QUADSIZE/2
4286             game.sector.j=QUADSIZE/2
4287             newqad()
4288     # Get new commission 
4289     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4290     game.state.crew = FULLCREW
4291     prout(_("Starfleet puts you in command of another ship,"))
4292     prout(_("the Faerie Queene, which is antiquated but,"))
4293     prout(_("still useable."))
4294     if game.icrystl:
4295         prout(_("The dilithium crystals have been moved."))
4296     game.imine = False
4297     game.iscraft = "offship" # Galileo disappears 
4298     # Resupply ship 
4299     game.condition="docked"
4300     for l in range(NDEVICES): 
4301         game.damage[l] = 0.0
4302     game.damage[DSHUTTL] = -1
4303     game.energy = game.inenrg = 3000.0
4304     game.shield = game.inshld = 1250.0
4305     game.torps = game.intorps = 6
4306     game.lsupres=game.inlsr=3.0
4307     game.shldup=False
4308     game.warpfac=5.0
4309     return
4310
4311 # Code from planets.c begins here.
4312
4313 def consumeTime():
4314     "Abort a lengthy operation if an event interrupts it." 
4315     game.ididit = True
4316     events()
4317     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin: 
4318         return True
4319     return False
4320
4321 def survey():
4322     "Report on (uninhabited) planets in the galaxy."
4323     iknow = False
4324     skip(1)
4325     scanner.chew()
4326     prout(_("Spock-  \"Planet report follows, Captain.\""))
4327     skip(1)
4328     for i in range(game.inplan):
4329         if game.state.planets[i].pclass == "destroyed":
4330             continue
4331         if (game.state.planets[i].known != "unknown" \
4332             and not game.state.planets[i].inhabited) \
4333             or game.idebug:
4334             iknow = True
4335             if game.idebug and game.state.planets[i].known=="unknown":
4336                 proutn("(Unknown) ")
4337             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4338             proutn(_("   class "))
4339             proutn(game.state.planets[i].pclass)
4340             proutn("   ")
4341             if game.state.planets[i].crystals != "present":
4342                 proutn(_("no "))
4343             prout(_("dilithium crystals present."))
4344             if game.state.planets[i].known=="shuttle_down": 
4345                 prout(_("    Shuttle Craft Galileo on surface."))
4346     if not iknow:
4347         prout(_("No information available."))
4348
4349 def orbit():
4350     "Enter standard orbit." 
4351     skip(1)
4352     scanner.chew()
4353     if game.inorbit:
4354         prout(_("Already in standard orbit."))
4355         return
4356     if damaged(DWARPEN) and damaged(DIMPULS):
4357         prout(_("Both warp and impulse engines damaged."))
4358         return
4359     if not game.plnet.is_valid():
4360         prout("There is no planet in this sector.")
4361         return
4362     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4363         prout(crmshp() + _(" not adjacent to planet."))
4364         skip(1)
4365         return
4366     game.optime = randreal(0.02, 0.05)
4367     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4368     newcnd()
4369     if consumeTime():
4370         return
4371     game.height = randreal(1400, 8600)
4372     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4373     game.inorbit = True
4374     game.ididit = True
4375
4376 def sensor():
4377     "Examine planets in this quadrant."
4378     if damaged(DSRSENS):
4379         if game.options & OPTION_TTY:
4380             prout(_("Short range sensors damaged."))
4381         return
4382     if game.iplnet == None:
4383         if game.options & OPTION_TTY:
4384             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4385         return
4386     if game.iplnet.known == "unknown":
4387         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4388         skip(1)
4389         prout(_("         Planet at Sector %s is of class %s.") %
4390               (game.plnet, game.iplnet.pclass))
4391         if game.iplnet.known=="shuttle_down": 
4392             prout(_("         Sensors show Galileo still on surface."))
4393         proutn(_("         Readings indicate"))
4394         if game.iplnet.crystals != "present":
4395             proutn(_(" no"))
4396         prout(_(" dilithium crystals present.\""))
4397         if game.iplnet.known == "unknown":
4398             game.iplnet.known = "known"
4399     elif game.iplnet.inhabited:
4400         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4401         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4402
4403 def beam():
4404     "Use the transporter."
4405     nrgneed = 0
4406     scanner.chew()
4407     skip(1)
4408     if damaged(DTRANSP):
4409         prout(_("Transporter damaged."))
4410         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4411             skip(1)
4412             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4413             if ja():
4414                 shuttle()
4415         return
4416     if not game.inorbit:
4417         prout(crmshp() + _(" not in standard orbit."))
4418         return
4419     if game.shldup:
4420         prout(_("Impossible to transport through shields."))
4421         return
4422     if game.iplnet.known=="unknown":
4423         prout(_("Spock-  \"Captain, we have no information on this planet"))
4424         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4425         prout(_("  you may not go down.\""))
4426         return
4427     if not game.landed and game.iplnet.crystals=="absent":
4428         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4429         prout(_("  exploring a planet with no dilithium crystals."))
4430         proutn(_("  Are you sure this is wise?\" "))
4431         if not ja():
4432             scanner.chew()
4433             return
4434     if not (game.options & OPTION_PLAIN):
4435         nrgneed = 50 * game.skill + game.height / 100.0
4436         if nrgneed > game.energy:
4437             prout(_("Engineering to bridge--"))
4438             prout(_("  Captain, we don't have enough energy for transportation."))
4439             return
4440         if not game.landed and nrgneed * 2 > game.energy:
4441             prout(_("Engineering to bridge--"))
4442             prout(_("  Captain, we have enough energy only to transport you down to"))
4443             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4444             if game.iplnet.known == "shuttle_down":
4445                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4446             proutn(_("  Are you sure this is wise?\" "))
4447             if not ja():
4448                 scanner.chew()
4449                 return
4450     if game.landed:
4451         # Coming from planet 
4452         if game.iplnet.known=="shuttle_down":
4453             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4454             if ja():
4455                 scanner.chew()
4456                 return
4457             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4458         prout(_("Landing party assembled, ready to beam up."))
4459         skip(1)
4460         prout(_("Kirk whips out communicator..."))
4461         prouts(_("BEEP  BEEP  BEEP"))
4462         skip(2)
4463         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4464     else:
4465         # Going to planet 
4466         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4467         skip(1)
4468         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4469         skip(1)
4470         prout(_("Kirk-  \"Energize.\""))
4471     game.ididit = True
4472     skip(1)
4473     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4474     skip(2)
4475     if withprob(0.98):
4476         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4477         skip(2)
4478         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4479         finish(FLOST)
4480         return
4481     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4482     game.landed = not game.landed
4483     game.energy -= nrgneed
4484     skip(2)
4485     prout(_("Transport complete."))
4486     if game.landed and game.iplnet.known=="shuttle_down":
4487         prout(_("The shuttle craft Galileo is here!"))
4488     if not game.landed and game.imine:
4489         game.icrystl = True
4490         game.cryprob = 0.05
4491     game.imine = False
4492     return
4493
4494 def mine():
4495     "Strip-mine a world for dilithium."
4496     skip(1)
4497     scanner.chew()
4498     if not game.landed:
4499         prout(_("Mining party not on planet."))
4500         return
4501     if game.iplnet.crystals == "mined":
4502         prout(_("This planet has already been strip-mined for dilithium."))
4503         return
4504     elif game.iplnet.crystals == "absent":
4505         prout(_("No dilithium crystals on this planet."))
4506         return
4507     if game.imine:
4508         prout(_("You've already mined enough crystals for this trip."))
4509         return
4510     if game.icrystl and game.cryprob == 0.05:
4511         prout(_("With all those fresh crystals aboard the ") + crmshp())
4512         prout(_("there's no reason to mine more at this time."))
4513         return
4514     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4515     if consumeTime():
4516         return
4517     prout(_("Mining operation complete."))
4518     game.iplnet.crystals = "mined"
4519     game.imine = game.ididit = True
4520
4521 def usecrystals():
4522     "Use dilithium crystals."
4523     game.ididit = False
4524     skip(1)
4525     scanner.chew()
4526     if not game.icrystl:
4527         prout(_("No dilithium crystals available."))
4528         return
4529     if game.energy >= 1000:
4530         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4531         prout(_("  except when Condition Yellow exists."))
4532         return
4533     prout(_("Spock- \"Captain, I must warn you that loading"))
4534     prout(_("  raw dilithium crystals into the ship's power"))
4535     prout(_("  system may risk a severe explosion."))
4536     proutn(_("  Are you sure this is wise?\" "))
4537     if not ja():
4538         scanner.chew()
4539         return
4540     skip(1)
4541     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4542     prout(_("  Mr. Spock and I will try it.\""))
4543     skip(1)
4544     prout(_("Spock-  \"Crystals in place, Sir."))
4545     prout(_("  Ready to activate circuit.\""))
4546     skip(1)
4547     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4548     skip(1)
4549     if withprob(game.cryprob):
4550         prouts(_("  \"Activating now! - - No good!  It's***"))
4551         skip(2)
4552         prouts(_("***RED ALERT!  RED A*L********************************"))
4553         skip(1)
4554         stars()
4555         prouts(_("******************   KA-BOOM!!!!   *******************"))
4556         skip(1)
4557         kaboom()
4558         return
4559     game.energy += randreal(5000.0, 5500.0)
4560     prouts(_("  \"Activating now! - - "))
4561     prout(_("The instruments"))
4562     prout(_("   are going crazy, but I think it's"))
4563     prout(_("   going to work!!  Congratulations, Sir!\""))
4564     game.cryprob *= 2.0
4565     game.ididit = True
4566
4567 def shuttle():
4568     "Use shuttlecraft for planetary jaunt."
4569     scanner.chew()
4570     skip(1)
4571     if damaged(DSHUTTL):
4572         if game.damage[DSHUTTL] == -1.0:
4573             if game.inorbit and game.iplnet.known == "shuttle_down":
4574                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4575             else:
4576                 prout(_("Ye Faerie Queene had no shuttle craft."))
4577         elif game.damage[DSHUTTL] > 0:
4578             prout(_("The Galileo is damaged."))
4579         else: # game.damage[DSHUTTL] < 0  
4580             prout(_("Shuttle craft is now serving Big Macs."))
4581         return
4582     if not game.inorbit:
4583         prout(crmshp() + _(" not in standard orbit."))
4584         return
4585     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4586         prout(_("Shuttle craft not currently available."))
4587         return
4588     if not game.landed and game.iplnet.known=="shuttle_down":
4589         prout(_("You will have to beam down to retrieve the shuttle craft."))
4590         return
4591     if game.shldup or game.condition == "docked":
4592         prout(_("Shuttle craft cannot pass through shields."))
4593         return
4594     if game.iplnet.known=="unknown":
4595         prout(_("Spock-  \"Captain, we have no information on this planet"))
4596         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4597         prout(_("  you may not fly down.\""))
4598         return
4599     game.optime = 3.0e-5*game.height
4600     if game.optime >= 0.8*game.state.remtime:
4601         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4602         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4603                int(100*game.optime/game.state.remtime))
4604         prout(_("remaining time."))
4605         proutn(_("Are you sure this is wise?\" "))
4606         if not ja():
4607             game.optime = 0.0
4608             return
4609     if game.landed:
4610         # Kirk on planet 
4611         if game.iscraft == "onship":
4612             # Galileo on ship! 
4613             if not damaged(DTRANSP):
4614                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4615                 if ja():
4616                     beam()
4617                     return
4618                 proutn(_("Shuttle crew"))
4619             else:
4620                 proutn(_("Rescue party"))
4621             prout(_(" boards Galileo and swoops toward planet surface."))
4622             game.iscraft = "offship"
4623             skip(1)
4624             if consumeTime():
4625                 return
4626             game.iplnet.known="shuttle_down"
4627             prout(_("Trip complete."))
4628             return
4629         else:
4630             # Ready to go back to ship 
4631             prout(_("You and your mining party board the"))
4632             prout(_("shuttle craft for the trip back to the Enterprise."))
4633             skip(1)
4634             prouts(_("The short hop begins . . ."))
4635             skip(1)
4636             game.iplnet.known="known"
4637             game.icraft = True
4638             skip(1)
4639             game.landed = False
4640             if consumeTime():
4641                 return
4642             game.iscraft = "onship"
4643             game.icraft = False
4644             if game.imine:
4645                 game.icrystl = True
4646                 game.cryprob = 0.05
4647             game.imine = False
4648             prout(_("Trip complete."))
4649             return
4650     else:
4651         # Kirk on ship and so is Galileo 
4652         prout(_("Mining party assembles in the hangar deck,"))
4653         prout(_("ready to board the shuttle craft \"Galileo\"."))
4654         skip(1)
4655         prouts(_("The hangar doors open; the trip begins."))
4656         skip(1)
4657         game.icraft = True
4658         game.iscraft = "offship"
4659         if consumeTime():
4660             return
4661         game.iplnet.known = "shuttle_down"
4662         game.landed = True
4663         game.icraft = False
4664         prout(_("Trip complete."))
4665         return
4666
4667 def deathray():
4668     "Use the big zapper."
4669     game.ididit = False
4670     skip(1)
4671     scanner.chew()
4672     if game.ship != 'E':
4673         prout(_("Ye Faerie Queene has no death ray."))
4674         return
4675     if len(game.enemies)==0:
4676         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4677         return
4678     if damaged(DDRAY):
4679         prout(_("Death Ray is damaged."))
4680         return
4681     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4682     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4683     proutn(_("  are you sure this is wise?\" "))
4684     if not ja():
4685         return
4686     prout(_("Spock-  \"Acknowledged.\""))
4687     skip(1)
4688     game.ididit = True
4689     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4690     skip(1)
4691     prout(_("Crew scrambles in emergency preparation."))
4692     prout(_("Spock and Scotty ready the death ray and"))
4693     prout(_("prepare to channel all ship's power to the device."))
4694     skip(1)
4695     prout(_("Spock-  \"Preparations complete, sir.\""))
4696     prout(_("Kirk-  \"Engage!\""))
4697     skip(1)
4698     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
4699     skip(1)
4700     dprob = 0.30
4701     if game.options & OPTION_PLAIN:
4702         dprob = 0.5
4703     r = randreal()
4704     if r > dprob:
4705         prouts(_("Sulu- \"Captain!  It's working!\""))
4706         skip(2)
4707         while len(game.enemies) > 0:
4708             deadkl(game.enemies[1].location, game.quad[game.enemies[1].location.i][game.enemies[1].location.j],game.enemies[1].location)
4709         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
4710         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
4711             finish(FWON)    
4712         if (game.options & OPTION_PLAIN) == 0:
4713             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
4714             if withprob(0.05):
4715                 prout(_("   is still operational.\""))
4716             else:
4717                 prout(_("   has been rendered nonfunctional.\""))
4718                 game.damage[DDRAY] = 39.95
4719         return
4720     r = randreal()      # Pick failure method 
4721     if r <= 0.30:
4722         prouts(_("Sulu- \"Captain!  It's working!\""))
4723         skip(1)
4724         prouts(_("***RED ALERT!  RED ALERT!"))
4725         skip(1)
4726         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
4727         skip(1)
4728         prouts(_("***RED ALERT!  RED A*L********************************"))
4729         skip(1)
4730         stars()
4731         prouts(_("******************   KA-BOOM!!!!   *******************"))
4732         skip(1)
4733         kaboom()
4734         return
4735     if r <= 0.55:
4736         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
4737         skip(1)
4738         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
4739         skip(1)
4740         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
4741         prout(_("  have apparently been transformed into strange mutations."))
4742         prout(_("  Vulcans do not seem to be affected."))
4743         skip(1)
4744         prout(_("Kirk-  \"Raauch!  Raauch!\""))
4745         finish(FDRAY)
4746         return
4747     if r <= 0.75:
4748         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
4749         skip(2)
4750         proutn(_("Spock-  \"I believe the word is"))
4751         prouts(_(" *ASTONISHING*"))
4752         prout(_(" Mr. Sulu."))
4753         for i in range(QUADSIZE):
4754             for j in range(QUADSIZE):
4755                 if game.quad[i][j] == '.':
4756                     game.quad[i][j] = '?'
4757         prout(_("  Captain, our quadrant is now infested with"))
4758         prouts(_(" - - - - - -  *THINGS*."))
4759         skip(1)
4760         prout(_("  I have no logical explanation.\""))
4761         return
4762     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
4763     skip(1)
4764     prout(_("Scotty-  \"There are so many tribbles down here"))
4765     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
4766     finish(FTRIBBLE)
4767     return
4768
4769 # Code from reports.c begins here
4770
4771 def attackreport(curt):
4772     "eport status of bases under attack."
4773     if not curt:
4774         if is_scheduled(FCDBAS):
4775             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
4776             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
4777         elif game.isatb == 1:
4778             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
4779             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
4780         else:
4781             prout(_("No Starbase is currently under attack."))
4782     else:
4783         if is_scheduled(FCDBAS):
4784             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
4785         if game.isatb:
4786             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
4787         clreol()
4788
4789 def report():
4790     # report on general game status 
4791     scanner.chew()
4792     s1 = "" and game.thawed and _("thawed ")
4793     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
4794     s3 = (None, _("novice"), _("fair"),
4795           _("good"), _("expert"), _("emeritus"))[game.skill]
4796     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
4797     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
4798         prout(_("No plaque is allowed."))
4799     if game.tourn:
4800         prout(_("This is tournament game %d.") % game.tourn)
4801     prout(_("Your secret password is \"%s\"") % game.passwd)
4802     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)), 
4803            (game.inkling + game.incom + game.inscom)))
4804     if game.incom - len(game.state.kcmdr):
4805         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
4806     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
4807         prout(_(", but no Commanders."))
4808     else:
4809         prout(".")
4810     if game.skill > SKILL_FAIR:
4811         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
4812     if len(game.state.baseq) != game.inbase:
4813         proutn(_("There "))
4814         if game.inbase-len(game.state.baseq)==1:
4815             proutn(_("has been 1 base"))
4816         else:
4817             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
4818         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
4819     else:
4820         prout(_("There are %d bases.") % game.inbase)
4821     if communicating() or game.iseenit:
4822         # Don't report this if not seen and
4823         # either the radio is dead or not at base!
4824         attackreport(False)
4825         game.iseenit = True
4826     if game.casual: 
4827         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
4828     if game.nhelp:
4829         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
4830     if game.ship == 'E':
4831         proutn(_("You have "))
4832         if game.nprobes:
4833             proutn("%d" % (game.nprobes))
4834         else:
4835             proutn(_("no"))
4836         proutn(_(" deep space probe"))
4837         if game.nprobes!=1:
4838             proutn(_("s"))
4839         prout(".")
4840     if communicating() and is_scheduled(FDSPROB):
4841         if game.isarmed: 
4842             proutn(_("An armed deep space probe is in "))
4843         else:
4844             proutn(_("A deep space probe is in "))
4845         prout("Quadrant %s." % game.probec)
4846     if game.icrystl:
4847         if game.cryprob <= .05:
4848             prout(_("Dilithium crystals aboard ship... not yet used."))
4849         else:
4850             i=0
4851             ai = 0.05
4852             while game.cryprob > ai:
4853                 ai *= 2.0
4854                 i += 1
4855             prout(_("Dilithium crystals have been used %d time%s.") % \
4856                   (i, (_("s"), "")[i==1]))
4857     skip(1)
4858         
4859 def lrscan(silent):
4860     "Long-range sensor scan."
4861     if damaged(DLRSENS):
4862         # Now allow base's sensors if docked 
4863         if game.condition != "docked":
4864             if not silent:
4865                 prout(_("LONG-RANGE SENSORS DAMAGED."))
4866             return
4867         if not silent:
4868             prout(_("Starbase's long-range scan"))
4869     elif not silent:
4870         prout(_("Long-range scan"))
4871     for x in range(game.quadrant.i-1, game.quadrant.i+2):
4872         if not silent:
4873             proutn(" ")
4874         for y in range(game.quadrant.j-1, game.quadrant.j+2):
4875             if not Coord(x, y).valid_quadrant():
4876                 if not silent:
4877                     proutn("  -1")
4878             else:
4879                 if not damaged(DRADIO):
4880                     game.state.galaxy[x][y].charted = True
4881                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
4882                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
4883                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
4884                 if not silent and game.state.galaxy[x][y].supernova: 
4885                     proutn(" ***")
4886                 elif not silent:
4887                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
4888         if not silent:
4889             prout(" ")
4890
4891 def damagereport():
4892     "Damage report."
4893     jdam = False
4894     scanner.chew()
4895     for i in range(NDEVICES):
4896         if damaged(i):
4897             if not jdam:
4898                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
4899                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
4900                 jdam = True
4901             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
4902                                                game.damage[i]+0.05,
4903                                                DOCKFAC*game.damage[i]+0.005))
4904     if not jdam:
4905         prout(_("All devices functional."))
4906
4907 def rechart():
4908     "Update the chart in the Enterprise's computer from galaxy data."
4909     game.lastchart = game.state.date
4910     for i in range(GALSIZE):
4911         for j in range(GALSIZE):
4912             if game.state.galaxy[i][j].charted:
4913                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
4914                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
4915                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
4916
4917 def chart():
4918     "Display the star chart."
4919     scanner.chew()
4920     if (game.options & OPTION_AUTOSCAN):
4921         lrscan(silent=True)
4922     if not damaged(DRADIO):
4923         rechart()
4924     if game.lastchart < game.state.date and game.condition == "docked":
4925         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
4926         rechart()
4927     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
4928     if game.state.date > game.lastchart:
4929         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
4930     prout("      1    2    3    4    5    6    7    8")
4931     for i in range(GALSIZE):
4932         proutn("%d |" % (i+1))
4933         for j in range(GALSIZE):
4934             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
4935                 proutn("<")
4936             else:
4937                 proutn(" ")
4938             if game.state.galaxy[i][j].supernova:
4939                 show = "***"
4940             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
4941                 show = ".1."
4942             elif game.state.galaxy[i][j].charted:
4943                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
4944             else:
4945                 show = "..."
4946             proutn(show)
4947             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
4948                 proutn(">")
4949             else:
4950                 proutn(" ")
4951         proutn("  |")
4952         if i<GALSIZE:
4953             skip(1)
4954
4955 def sectscan(goodScan, i, j):
4956     "Light up an individual dot in a sector."
4957     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
4958         textcolor({"green":GREEN,
4959                    "yellow":YELLOW,
4960                    "red":RED,
4961                    "docked":CYAN,
4962                    "dead":BROWN}[game.condition]) 
4963         if game.quad[i][j] != game.ship: 
4964             highvideo();
4965         proutn("%c " % game.quad[i][j])
4966         textcolor(DEFAULT)
4967     else:
4968         proutn("- ")
4969
4970 def status(req=0):
4971     "Emit status report lines"
4972     if not req or req == 1:
4973         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
4974                % (game.state.date, game.state.remtime))
4975     if not req or req == 2:
4976         if game.condition != "docked":
4977             newcnd()
4978         prstat(_("Condition"), _("%s, %i DAMAGES") % \
4979                (game.condition.upper(), sum(map(lambda x: x > 0, game.damage))))
4980     if not req or req == 3:
4981         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
4982     if not req or req == 4:
4983         if damaged(DLIFSUP):
4984             if game.condition == "docked":
4985                 s = _("DAMAGED, Base provides")
4986             else:
4987                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
4988         else:
4989             s = _("ACTIVE")
4990         prstat(_("Life Support"), s)
4991     if not req or req == 5:
4992         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
4993     if not req or req == 6:
4994         extra = ""
4995         if game.icrystl and (game.options & OPTION_SHOWME):
4996             extra = _(" (have crystals)")
4997         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
4998     if not req or req == 7:
4999         prstat(_("Torpedoes"), "%d" % (game.torps))
5000     if not req or req == 8:
5001         if damaged(DSHIELD):
5002             s = _("DAMAGED,")
5003         elif game.shldup:
5004             s = _("UP,")
5005         else:
5006             s = _("DOWN,")
5007         data = _(" %d%% %.1f units") \
5008                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5009         prstat(_("Shields"), s+data)
5010     if not req or req == 9:
5011         prstat(_("Klingons Left"), "%d" \
5012                % (game.state.remkl+len(game.state.kcmdr)+game.state.nscrem))
5013     if not req or req == 10:
5014         if game.options & OPTION_WORLDS:
5015             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5016             if plnet and plnet.inhabited:
5017                 prstat(_("Major system"), plnet.name)
5018             else:
5019                 prout(_("Sector is uninhabited"))
5020     elif not req or req == 11:
5021         attackreport(not req)
5022
5023 def request():
5024     "Request specified status data, a historical relic from slow TTYs."
5025     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5026     while scanner.next() == "IHEOL":
5027         proutn(_("Information desired? "))
5028     scanner.chew()
5029     if scanner.token in requests:
5030         status(requests.index(scanner.token))
5031     else:
5032         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5033         prout(("  date, condition, position, lsupport, warpfactor,"))
5034         prout(("  energy, torpedoes, shields, klingons, system, time."))
5035                 
5036 def srscan():
5037     "Short-range scan." 
5038     goodScan=True
5039     if damaged(DSRSENS):
5040         # Allow base's sensors if docked 
5041         if game.condition != "docked":
5042             prout(_("   S.R. SENSORS DAMAGED!"))
5043             goodScan=False
5044         else:
5045             prout(_("  [Using Base's sensors]"))
5046     else:
5047         prout(_("     Short-range scan"))
5048     if goodScan and not damaged(DRADIO): 
5049         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5050         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5051         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5052         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5053     prout("    1 2 3 4 5 6 7 8 9 10")
5054     if game.condition != "docked":
5055         newcnd()
5056     for i in range(QUADSIZE):
5057         proutn("%2d  " % (i+1))
5058         for j in range(QUADSIZE):
5059             sectscan(goodScan, i, j)
5060         skip(1)
5061                 
5062 def eta():
5063     "Use computer to get estimated time of arrival for a warp jump."
5064     w1 = Coord(); w2 = Coord()
5065     prompt = False
5066     if damaged(DCOMPTR):
5067         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5068         skip(1)
5069         return
5070     if scanner.next() != "IHREAL":
5071         prompt = True
5072         scanner.chew()
5073         proutn(_("Destination quadrant and/or sector? "))
5074         if scanner.next()!="IHREAL":
5075             huh()
5076             return
5077     w1.j = int(scanner.real-0.5)
5078     if scanner.next() != "IHREAL":
5079         huh()
5080         return
5081     w1.i = int(scanner.real-0.5)
5082     if scanner.next() == "IHREAL":
5083         w2.j = int(scanner.real-0.5)
5084         if scanner.next() != "IHREAL":
5085             huh()
5086             return
5087         w2.i = int(scanner.real-0.5)
5088     else:
5089         if game.quadrant.j>w1.i:
5090             w2.i = 0
5091         else:
5092             w2.i=QUADSIZE-1
5093         if game.quadrant.i>w1.j:
5094             w2.j = 0
5095         else:
5096             w2.j=QUADSIZE-1
5097     if not w1.valid_quadrant() or not w2.valid_sector():
5098         huh()
5099         return
5100     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5101                 (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5102     wfl = False
5103     if prompt:
5104         prout(_("Answer \"no\" if you don't know the value:"))
5105     while True:
5106         scanner.chew()
5107         proutn(_("Time or arrival date? "))
5108         if scanner.next()=="IHREAL":
5109             ttime = scanner.real
5110             if ttime > game.state.date:
5111                 ttime -= game.state.date # Actually a star date
5112             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5113             if ttime <= 1e-10 or twarp > 10:
5114                 prout(_("We'll never make it, sir."))
5115                 scanner.chew()
5116                 return
5117             if twarp < 1.0:
5118                 twarp = 1.0
5119             break
5120         scanner.chew()
5121         proutn(_("Warp factor? "))
5122         if scanner.next()== "IHREAL":
5123             wfl = True
5124             twarp = scanner.real
5125             if twarp<1.0 or twarp > 10.0:
5126                 huh()
5127                 return
5128             break
5129         prout(_("Captain, certainly you can give me one of these."))
5130     while True:
5131         scanner.chew()
5132         ttime = (10.0*dist)/twarp**2
5133         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5134         if tpower >= game.energy:
5135             prout(_("Insufficient energy, sir."))
5136             if not game.shldup or tpower > game.energy*2.0:
5137                 if not wfl:
5138                     return
5139                 proutn(_("New warp factor to try? "))
5140                 if scanner.next() == "IHREAL":
5141                     wfl = True
5142                     twarp = scanner.real
5143                     if twarp<1.0 or twarp > 10.0:
5144                         huh()
5145                         return
5146                     continue
5147                 else:
5148                     scanner.chew()
5149                     skip(1)
5150                     return
5151             prout(_("But if you lower your shields,"))
5152             proutn(_("remaining"))
5153             tpower /= 2
5154         else:
5155             proutn(_("Remaining"))
5156         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5157         if wfl:
5158             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5159         elif twarp==1.0:
5160             prout(_("Any warp speed is adequate."))
5161         else:
5162             prout(_("Minimum warp needed is %.2f,") % (twarp))
5163             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5164         if game.state.remtime < ttime:
5165             prout(_("Unfortunately, the Federation will be destroyed by then."))
5166         if twarp > 6.0:
5167             prout(_("You'll be taking risks at that speed, Captain"))
5168         if (game.isatb==1 and game.state.kscmdr == w1 and \
5169              scheduled(FSCDBAS)< ttime+game.state.date) or \
5170             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5171             prout(_("The starbase there will be destroyed by then."))
5172         proutn(_("New warp factor to try? "))
5173         if scanner.next() == "IHREAL":
5174             wfl = True
5175             twarp = scanner.real
5176             if twarp<1.0 or twarp > 10.0:
5177                 huh()
5178                 return
5179         else:
5180             scanner.chew()
5181             skip(1)
5182             return
5183
5184 # Code from setup.c begins here
5185
5186 def prelim():
5187     "Issue a historically correct banner."
5188     skip(2)
5189     prout(_("-SUPER- STAR TREK"))
5190     skip(1)
5191 # From the FORTRAN original
5192 #    prout(_("Latest update-21 Sept 78"))
5193 #    skip(1)
5194
5195 def freeze(boss):
5196     "Save game."
5197     if boss:
5198         scanner.push("emsave.trk")
5199     key = scanner.next()
5200     if key == "IHEOL":
5201         proutn(_("File name: "))
5202         key = scanner.next()
5203     if key != "IHALPHA":
5204         huh()
5205         return
5206     scanner.chew()
5207     if '.' not in scanner.token:
5208         scanner.token += ".trk"
5209     try:
5210         fp = open(scanner.token, "wb")
5211     except IOError:
5212         prout(_("Can't freeze game as file %s") % scanner.token)
5213         return
5214     cPickle.dump(game, fp)
5215     fp.close()
5216
5217 def thaw():
5218     "Retrieve saved game."
5219     game.passwd[0] = '\0'
5220     key = scanner.next()
5221     if key == "IHEOL":
5222         proutn(_("File name: "))
5223         key = scanner.next()
5224     if key != "IHALPHA":
5225         huh()
5226         return True
5227     scanner.chew()
5228     if '.' not in scanner.token:
5229         scanner.token += ".trk"
5230     try:
5231         fp = open(scanner.token, "rb")
5232     except IOError:
5233         prout(_("Can't thaw game in %s") % scanner.token)
5234         return
5235     game = cPickle.load(fp)
5236     fp.close()
5237     return False
5238
5239 # I used <http://www.memory-alpha.org> to find planets
5240 # with references in ST:TOS.  Earth and the Alpha Centauri
5241 # Colony have been omitted.
5242
5243 # Some planets marked Class G and P here will be displayed as class M
5244 # because of the way planets are generated. This is a known bug.
5245 systnames = (
5246     # Federation Worlds 
5247     _("Andoria (Fesoan)"),      # several episodes 
5248     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel" 
5249     _("Vulcan (T'Khasi)"),      # many episodes 
5250     _("Medusa"),                # TOS: "Is There in Truth No Beauty?" 
5251     _("Argelius II (Nelphia)"), # TOS: "Wolf in the Fold" ("IV" in BSD) 
5252     _("Ardana"),                # TOS: "The Cloud Minders" 
5253     _("Catulla (Cendo-Prae)"),  # TOS: "The Way to Eden" 
5254     _("Gideon"),                # TOS: "The Mark of Gideon" 
5255     _("Aldebaran III"),         # TOS: "The Deadly Years" 
5256     _("Alpha Majoris I"),       # TOS: "Wolf in the Fold" 
5257     _("Altair IV"),             # TOS: "Amok Time 
5258     _("Ariannus"),              # TOS: "Let That Be Your Last Battlefield" 
5259     _("Benecia"),               # TOS: "The Conscience of the King" 
5260     _("Beta Niobe I (Sarpeidon)"),      # TOS: "All Our Yesterdays" 
5261     _("Alpha Carinae II"),      # TOS: "The Ultimate Computer" 
5262     _("Capella IV (Kohath)"),   # TOS: "Friday's Child" (Class G) 
5263     _("Daran V"),               # TOS: "For the World is Hollow and I Have Touched the Sky" 
5264     _("Deneb II"),              # TOS: "Wolf in the Fold" ("IV" in BSD) 
5265     _("Eminiar VII"),           # TOS: "A Taste of Armageddon" 
5266     _("Gamma Canaris IV"),      # TOS: "Metamorphosis" 
5267     _("Gamma Tranguli VI (Vaalel)"),    # TOS: "The Apple" 
5268     _("Ingraham B"),            # TOS: "Operation: Annihilate" 
5269     _("Janus IV"),              # TOS: "The Devil in the Dark" 
5270     _("Makus III"),             # TOS: "The Galileo Seven" 
5271     _("Marcos XII"),            # TOS: "And the Children Shall Lead", 
5272     _("Omega IV"),              # TOS: "The Omega Glory" 
5273     _("Regulus V"),             # TOS: "Amok Time 
5274     _("Deneva"),                # TOS: "Operation -- Annihilate!" 
5275     # Worlds from BSD Trek 
5276     _("Rigel II"),              # TOS: "Shore Leave" ("III" in BSD) 
5277     _("Beta III"),              # TOS: "The Return of the Archons" 
5278     _("Triacus"),               # TOS: "And the Children Shall Lead", 
5279     _("Exo III"),               # TOS: "What Are Little Girls Made Of?" (Class P) 
5280 #       # Others 
5281 #    _("Hansen's Planet"),      # TOS: "The Galileo Seven" 
5282 #    _("Taurus IV"),            # TOS: "The Galileo Seven" (class G) 
5283 #    _("Antos IV (Doraphane)"), # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?" 
5284 #    _("Izar"),                 # TOS: "Whom Gods Destroy" 
5285 #    _("Tiburon"),              # TOS: "The Way to Eden" 
5286 #    _("Merak II"),             # TOS: "The Cloud Minders" 
5287 #    _("Coridan (Desotriana)"), # TOS: "Journey to Babel" 
5288 #    _("Iotia"),                # TOS: "A Piece of the Action" 
5289 )
5290
5291 device = (
5292         _("S. R. Sensors"), \
5293         _("L. R. Sensors"), \
5294         _("Phasers"), \
5295         _("Photon Tubes"), \
5296         _("Life Support"), \
5297         _("Warp Engines"), \
5298         _("Impulse Engines"), \
5299         _("Shields"), \
5300         _("Subspace Radio"), \
5301         _("Shuttle Craft"), \
5302         _("Computer"), \
5303         _("Navigation System"), \
5304         _("Transporter"), \
5305         _("Shield Control"), \
5306         _("Death Ray"), \
5307         _("D. S. Probe"), \
5308 )
5309
5310 def setup():
5311     "Prepare to play, set up cosmos."
5312     w = Coord()
5313     #  Decide how many of everything
5314     if choose():
5315         return # frozen game
5316     # Prepare the Enterprise
5317     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5318     game.ship = 'E'
5319     game.state.crew = FULLCREW
5320     game.energy = game.inenrg = 5000.0
5321     game.shield = game.inshld = 2500.0
5322     game.inlsr = 4.0
5323     game.lsupres = 4.0
5324     game.quadrant = randplace(GALSIZE)
5325     game.sector = randplace(QUADSIZE)
5326     game.torps = game.intorps = 10
5327     game.nprobes = randrange(2, 5)
5328     game.warpfac = 5.0
5329     for i in range(NDEVICES): 
5330         game.damage[i] = 0.0
5331     # Set up assorted game parameters
5332     game.battle = Coord()
5333     game.state.date = game.indate = 100.0 * randreal(20, 51)
5334     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5335     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5336     game.isatb = game.state.nplankl = 0
5337     game.state.starkl = game.state.basekl = 0
5338     game.iscraft = "onship"
5339     game.landed = False
5340     game.alive = True
5341     # Starchart is functional but we've never seen it
5342     game.lastchart = FOREVER
5343     # Put stars in the galaxy
5344     game.instar = 0
5345     for i in range(GALSIZE):
5346         for j in range(GALSIZE):
5347             k = randrange(1, QUADSIZE**2/10+1)
5348             game.instar += k
5349             game.state.galaxy[i][j].stars = k
5350     # Locate star bases in galaxy
5351     for i in range(game.inbase):
5352         while True:
5353             while True:
5354                 w = randplace(GALSIZE)
5355                 if not game.state.galaxy[w.i][w.j].starbase:
5356                     break
5357             contflag = False
5358             # C version: for (j = i-1; j > 0; j--)
5359             # so it did them in the opposite order.
5360             for j in range(1, i):
5361                 # Improved placement algorithm to spread out bases
5362                 distq = (w - game.state.baseq[j]).distance()
5363                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5364                     contflag = True
5365                     if game.idebug:
5366                         prout("=== Abandoning base #%d at %s" % (i, w))
5367                     break
5368                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5369                     if game.idebug:
5370                         prout("=== Saving base #%d, close to #%d" % (i, j))
5371             if not contflag:
5372                 break
5373         game.state.baseq.append(w)
5374         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5375     # Position ordinary Klingon Battle Cruisers
5376     krem = game.inkling
5377     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5378     if klumper > MAXKLQUAD: 
5379         klumper = MAXKLQUAD
5380     while True:
5381         r = randreal()
5382         klump = (1.0 - r*r)*klumper
5383         if klump > krem:
5384             klump = krem
5385         krem -= klump
5386         while True:
5387             w = randplace(GALSIZE)
5388             if not game.state.galaxy[w.i][w.j].supernova and \
5389                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5390                 break
5391         game.state.galaxy[w.i][w.j].klingons += int(klump)
5392         if krem <= 0:
5393             break
5394     # Position Klingon Commander Ships
5395     for i in range(game.incom):
5396         while True:
5397             w = randplace(GALSIZE)
5398             if not welcoming(w) or w in game.state.kcmdr:
5399                 continue
5400             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5401                 break
5402         game.state.galaxy[w.i][w.j].klingons += 1
5403         game.state.kcmdr.append(w)
5404     # Locate planets in galaxy
5405     for i in range(game.inplan):
5406         while True:
5407             w = randplace(GALSIZE) 
5408             if game.state.galaxy[w.i][w.j].planet == None:
5409                 break
5410         new = Planet()
5411         new.quadrant = w
5412         new.crystals = "absent"
5413         if (game.options & OPTION_WORLDS) and i < NINHAB:
5414             new.pclass = "M"    # All inhabited planets are class M
5415             new.crystals = "absent"
5416             new.known = "known"
5417             new.name = systnames[i]
5418             new.inhabited = True
5419         else:
5420             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5421             if withprob(0.33):
5422                 new.crystals = "present"
5423             new.known = "unknown"
5424             new.inhabited = False
5425         game.state.galaxy[w.i][w.j].planet = new
5426         game.state.planets.append(new)
5427     # Locate Romulans
5428     for i in range(game.state.nromrem):
5429         w = randplace(GALSIZE)
5430         game.state.galaxy[w.i][w.j].romulans += 1
5431     # Place the Super-Commander if needed
5432     if game.state.nscrem > 0:
5433         while True:
5434             w = randplace(GALSIZE)
5435             if welcoming(w):
5436                 break
5437         game.state.kscmdr = w
5438         game.state.galaxy[w.i][w.j].klingons += 1
5439     # Initialize times for extraneous events
5440     schedule(FSNOVA, expran(0.5 * game.intime))
5441     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5442     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5443     schedule(FBATTAK, expran(0.3*game.intime))
5444     unschedule(FCDBAS)
5445     if game.state.nscrem:
5446         schedule(FSCMOVE, 0.2777)
5447     else:
5448         unschedule(FSCMOVE)
5449     unschedule(FSCDBAS)
5450     unschedule(FDSPROB)
5451     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5452         schedule(FDISTR, expran(1.0 + game.intime))
5453     else:
5454         unschedule(FDISTR)
5455     unschedule(FENSLV)
5456     unschedule(FREPRO)
5457     # Place thing (in tournament game, we don't want one!)
5458     # New in SST2K: never place the Thing near a starbase.
5459     # This makes sense and avoids a special case in the old code.
5460     global thing
5461     if game.tourn is None:
5462         while True:
5463             thing = randplace(GALSIZE)
5464             if thing not in game.state.baseq:
5465                 break
5466     skip(2)
5467     game.state.snap = False
5468     if game.skill == SKILL_NOVICE:
5469         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5470         prout(_("a deadly Klingon invasion force. As captain of the United"))
5471         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5472         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5473         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5474         prout(_("your mission.  As you proceed you may be given more time."))
5475         skip(1)
5476         prout(_("You will have %d supporting starbases.") % (game.inbase))
5477         proutn(_("Starbase locations-  "))
5478     else:
5479         prout(_("Stardate %d.") % int(game.state.date))
5480         skip(1)
5481         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5482         prout(_("An unknown number of Romulans."))
5483         if game.state.nscrem:
5484             prout(_("And one (GULP) Super-Commander."))
5485         prout(_("%d stardates.") % int(game.intime))
5486         proutn(_("%d starbases in ") % game.inbase)
5487     for i in range(game.inbase):
5488         proutn(`game.state.baseq[i]`)
5489         proutn("  ")
5490     skip(2)
5491     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5492     proutn(_(" Sector %s") % game.sector)
5493     skip(2)
5494     prout(_("Good Luck!"))
5495     if game.state.nscrem:
5496         prout(_("  YOU'LL NEED IT."))
5497     waitfor()
5498     newqad()
5499     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5500         game.shldup = True
5501     if game.neutz:      # bad luck to start in a Romulan Neutral Zone
5502         attack(torps_ok=False)
5503
5504 def choose():
5505     "Choose your game type."
5506     while True:
5507         game.tourn = game.length = 0
5508         game.thawed = False
5509         game.skill = SKILL_NONE
5510         if not scanner.inqueue: # Can start with command line options 
5511             proutn(_("Would you like a regular, tournament, or saved game? "))
5512         scanner.next()
5513         if scanner.sees("tournament"):
5514             while scanner.next() == "IHEOL":
5515                 proutn(_("Type in tournament number-"))
5516             if scanner.real == 0:
5517                 scanner.chew()
5518                 continue # We don't want a blank entry
5519             game.tourn = int(round(scanner.real))
5520             random.seed(scanner.real)
5521             if logfp:
5522                 logfp.write("# random.seed(%d)\n" % scanner.real)
5523             break
5524         if scanner.sees("saved") or scanner.sees("frozen"):
5525             if thaw():
5526                 continue
5527             scanner.chew()
5528             if game.passwd == None:
5529                 continue
5530             if not game.alldone:
5531                 game.thawed = True # No plaque if not finished
5532             report()
5533             waitfor()
5534             return True
5535         if scanner.sees("regular"):
5536             break
5537         proutn(_("What is \"%s\"?") % scanner.token)
5538         scanner.chew()
5539     while game.length==0 or game.skill==SKILL_NONE:
5540         if scanner.next() == "IHALPHA":
5541             if scanner.sees("short"):
5542                 game.length = 1
5543             elif scanner.sees("medium"):
5544                 game.length = 2
5545             elif scanner.sees("long"):
5546                 game.length = 4
5547             elif scanner.sees("novice"):
5548                 game.skill = SKILL_NOVICE
5549             elif scanner.sees("fair"):
5550                 game.skill = SKILL_FAIR
5551             elif scanner.sees("good"):
5552                 game.skill = SKILL_GOOD
5553             elif scanner.sees("expert"):
5554                 game.skill = SKILL_EXPERT
5555             elif scanner.sees("emeritus"):
5556                 game.skill = SKILL_EMERITUS
5557             else:
5558                 proutn(_("What is \""))
5559                 proutn(scanner.token)
5560                 prout("\"?")
5561         else:
5562             scanner.chew()
5563             if game.length==0:
5564                 proutn(_("Would you like a Short, Medium, or Long game? "))
5565             elif game.skill == SKILL_NONE:
5566                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5567     # Choose game options -- added by ESR for SST2K
5568     if scanner.next() != "IHALPHA":
5569         scanner.chew()
5570         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5571         scanner.next()
5572     if scanner.sees("plain"):
5573         # Approximates the UT FORTRAN version.
5574         game.options &=~ (OPTION_THOLIAN | OPTION_PLANETS | OPTION_THINGY | OPTION_PROBE | OPTION_RAMMING | OPTION_MVBADDY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5575         game.options |= OPTION_PLAIN
5576     elif scanner.sees("almy"):
5577         # Approximates Tom Almy's version.
5578         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS)
5579         game.options |= OPTION_ALMY
5580     elif scanner.sees("fancy") or scanner.sees("\n"):
5581         pass
5582     elif len(scanner.token):
5583         proutn(_("What is \"%s\"?") % scanner.token)
5584     game.options &=~ OPTION_COLOR
5585     setpassword()
5586     if game.passwd == "debug":
5587         game.idebug = True
5588         prout("=== Debug mode enabled.")
5589     # Use parameters to generate initial values of things
5590     game.damfac = 0.5 * game.skill
5591     game.inbase = randrange(BASEMIN, BASEMAX+1)
5592     game.inplan = 0
5593     if game.options & OPTION_PLANETS:
5594         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5595     if game.options & OPTION_WORLDS:
5596         game.inplan += int(NINHAB)
5597     game.state.nromrem = game.inrom = randrange(2 *game.skill)
5598     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5599     game.state.remtime = 7.0 * game.length
5600     game.intime = game.state.remtime
5601     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5602     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5603     game.state.remres = (game.inkling+4*game.incom)*game.intime
5604     game.inresor = game.state.remres
5605     if game.inkling > 50:
5606         game.state.inbase += 1
5607     return False
5608
5609 def dropin(iquad=None):
5610     "Drop a feature on a random dot in the current quadrant."
5611     while True:
5612         w = randplace(QUADSIZE)
5613         if game.quad[w.i][w.j] == '.':
5614             break
5615     if iquad is not None:
5616         game.quad[w.i][w.j] = iquad
5617     return w
5618
5619 def newcnd():
5620     "Update our alert status."
5621     game.condition = "green"
5622     if game.energy < 1000.0:
5623         game.condition = "yellow"
5624     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5625         game.condition = "red"
5626     if not game.alive:
5627         game.condition="dead"
5628
5629 def newkling():
5630     "Drop new Klingon into current quadrant."
5631     return Enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
5632
5633 def newqad():
5634     "Set up a new state of quadrant, for when we enter or re-enter it."
5635     game.justin = True
5636     game.iplnet = None
5637     game.neutz = game.inorbit = game.landed = False
5638     game.ientesc = game.iseenit = False
5639     # Create a blank quadrant
5640     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
5641     if game.iscate:
5642         # Attempt to escape Super-commander, so tbeam back!
5643         game.iscate = False
5644         game.ientesc = True
5645     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
5646     # cope with supernova
5647     if q.supernova:
5648         return
5649     game.klhere = q.klingons
5650     game.irhere = q.romulans
5651     # Position Starship
5652     game.quad[game.sector.i][game.sector.j] = game.ship
5653     game.enemies = []
5654     if q.klingons:
5655         # Position ordinary Klingons
5656         for i in range(game.klhere):
5657             newkling()
5658         # If we need a commander, promote a Klingon
5659         for cmdr in game.state.kcmdr:
5660             if cmdr == game.quadrant:
5661                 e = game.enemies[game.klhere-1]
5662                 game.quad[e.location.i][e.location.j] = 'C'
5663                 e.power = randreal(950,1350) + 50.0*game.skill
5664                 break   
5665         # If we need a super-commander, promote a Klingon
5666         if game.quadrant == game.state.kscmdr:
5667             e = game.enemies[0]
5668             game.quad[e.location.i][e.location.j] = 'S'
5669             e.power = randreal(1175.0,  1575.0) + 125.0*game.skill
5670             game.iscate = (game.state.remkl > 1)
5671     # Put in Romulans if needed
5672     for i in range(q.romulans):
5673         Enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
5674     # If quadrant needs a starbase, put it in
5675     if q.starbase:
5676         game.base = dropin('B')
5677     # If quadrant needs a planet, put it in
5678     if q.planet:
5679         game.iplnet = q.planet
5680         if not q.planet.inhabited:
5681             game.plnet = dropin('P')
5682         else:
5683             game.plnet = dropin('@')
5684     # Check for condition
5685     newcnd()
5686     # Check for RNZ
5687     if game.irhere > 0 and game.klhere == 0:
5688         game.neutz = True
5689         if not damaged(DRADIO):
5690             skip(1)
5691             prout(_("LT. Uhura- \"Captain, an urgent message."))
5692             prout(_("  I'll put it on audio.\"  CLICK"))
5693             skip(1)
5694             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
5695             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
5696     # Put in THING if needed
5697     if thing == game.quadrant:
5698         Enemy(type='?', loc=dropin(),
5699                   power=randreal(6000,6500.0)+250.0*game.skill)
5700         if not damaged(DSRSENS):
5701             skip(1)
5702             prout(_("Mr. Spock- \"Captain, this is most unusual."))
5703             prout(_("    Please examine your short-range scan.\""))
5704     # Decide if quadrant needs a Tholian; lighten up if skill is low 
5705     if game.options & OPTION_THOLIAN:
5706         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
5707             (game.skill == SKILL_GOOD and withprob(0.05)) or \
5708             (game.skill > SKILL_GOOD and withprob(0.08)):
5709             w = Coord()
5710             while True:
5711                 w.i = withprob(0.5) * (QUADSIZE-1)
5712                 w.j = withprob(0.5) * (QUADSIZE-1)
5713                 if game.quad[w.i][w.j] == '.':
5714                     break
5715             game.tholian = Enemy(type='T', loc=w,
5716                                  power=randrange(100, 500) + 25.0*game.skill)
5717             # Reserve unoccupied corners 
5718             if game.quad[0][0]=='.':
5719                 game.quad[0][0] = 'X'
5720             if game.quad[0][QUADSIZE-1]=='.':
5721                 game.quad[0][QUADSIZE-1] = 'X'
5722             if game.quad[QUADSIZE-1][0]=='.':
5723                 game.quad[QUADSIZE-1][0] = 'X'
5724             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
5725                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
5726     game.enemies.sort(lambda x, y: cmp(x.kdist, y.kdist))
5727     # And finally the stars
5728     for i in range(q.stars):
5729         dropin('*')
5730     # Put in a few black holes
5731     for i in range(1, 3+1):
5732         if withprob(0.5): 
5733             dropin(' ')
5734     # Take out X's in corners if Tholian present
5735     if game.tholian:
5736         if game.quad[0][0]=='X':
5737             game.quad[0][0] = '.'
5738         if game.quad[0][QUADSIZE-1]=='X':
5739             game.quad[0][QUADSIZE-1] = '.'
5740         if game.quad[QUADSIZE-1][0]=='X':
5741             game.quad[QUADSIZE-1][0] = '.'
5742         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
5743             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
5744
5745 def setpassword():
5746     "Set the self-destruct password."
5747     if game.options & OPTION_PLAIN:
5748         while True:
5749             scanner.chew()
5750             proutn(_("Please type in a secret password- "))
5751             scanner.next()
5752             game.passwd = scanner.token
5753             if game.passwd != None:
5754                 break
5755     else:
5756         game.passwd = ""
5757         game.passwd += chr(ord('a')+randrange(26))
5758         game.passwd += chr(ord('a')+randrange(26))
5759         game.passwd += chr(ord('a')+randrange(26))
5760
5761 # Code from sst.c begins here
5762
5763 commands = {
5764     "SRSCAN":           OPTION_TTY,
5765     "STATUS":           OPTION_TTY,
5766     "REQUEST":          OPTION_TTY,
5767     "LRSCAN":           OPTION_TTY,
5768     "PHASERS":          0,
5769     "TORPEDO":          0,
5770     "PHOTONS":          0,
5771     "MOVE":             0,
5772     "SHIELDS":          0,
5773     "DOCK":             0,
5774     "DAMAGES":          0,
5775     "CHART":            0,
5776     "IMPULSE":          0,
5777     "REST":             0,
5778     "WARP":             0,
5779     "SCORE":            0,
5780     "SENSORS":          OPTION_PLANETS,
5781     "ORBIT":            OPTION_PLANETS,
5782     "TRANSPORT":        OPTION_PLANETS,
5783     "MINE":             OPTION_PLANETS,
5784     "CRYSTALS":         OPTION_PLANETS,
5785     "SHUTTLE":          OPTION_PLANETS,
5786     "PLANETS":          OPTION_PLANETS,
5787     "REPORT":           0,
5788     "COMPUTER":         0,
5789     "COMMANDS":         0,
5790     "EMEXIT":           0,
5791     "PROBE":            OPTION_PROBE,
5792     "SAVE":             0,
5793     "FREEZE":           0,      # Synonym for SAVE
5794     "ABANDON":          0,
5795     "DESTRUCT":         0,
5796     "DEATHRAY":         0,
5797     "DEBUG":            0,
5798     "MAYDAY":           0,
5799     "SOS":              0,      # Synonym for MAYDAY
5800     "CALL":             0,      # Synonym for MAYDAY
5801     "QUIT":             0,
5802     "HELP":             0,
5803 }
5804
5805 def listCommands():
5806     "Generate a list of legal commands."
5807     prout(_("LEGAL COMMANDS ARE:"))
5808     emitted = 0
5809     for key in commands:
5810         if not commands[key] or (commands[key] & game.options):
5811             proutn("%-12s " % key)
5812             emitted += 1
5813             if emitted % 5 == 4:
5814                 skip(1)
5815     skip(1)
5816
5817 def helpme():
5818     "Browse on-line help."
5819     key = scanner.next()
5820     while True:
5821         if key == "IHEOL":
5822             setwnd(prompt_window)
5823             proutn(_("Help on what command? "))
5824             key = scanner.next()
5825         setwnd(message_window)
5826         if key == "IHEOL":
5827             return
5828         if scanner.token.upper() in commands or scanner.token == "ABBREV":
5829             break
5830         skip(1)
5831         listCommands()
5832         key = "IHEOL"
5833         scanner.chew()
5834         skip(1)
5835     cmd = scanner.token.upper()
5836     for directory in docpath:
5837         try:
5838             fp = open(os.path.join(directory, "sst.doc"), "r")
5839             break
5840         except IOError:
5841             pass
5842     else:
5843         prout(_("Spock-  \"Captain, that information is missing from the"))
5844         prout(_("   computer. You need to find sst.doc and put it somewhere"))
5845         proutn(_("   in these directories: %s") % ":".join(docpath))
5846         prout(".\"")
5847         # This used to continue: "You need to find SST.DOC and put 
5848         # it in the current directory."
5849         return
5850     while True:
5851         linebuf = fp.readline()
5852         if linebuf == '':
5853             prout(_("Spock- \"Captain, there is no information on that command.\""))
5854             fp.close()
5855             return
5856         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
5857             linebuf = linebuf[3:].strip()
5858             if cmd.upper() == linebuf:
5859                 break
5860     skip(1)
5861     prout(_("Spock- \"Captain, I've found the following information:\""))
5862     skip(1)
5863     while True:
5864         linebuf = fp.readline()
5865         if "******" in linebuf:
5866             break
5867         proutn(linebuf)
5868     fp.close()
5869
5870 def makemoves():
5871     "Command-interpretation loop."
5872     clrscr()
5873     setwnd(message_window)
5874     while True:         # command loop 
5875         drawmaps(1)
5876         while True:     # get a command 
5877             hitme = False
5878             game.optime = game.justin = False
5879             scanner.chew()
5880             setwnd(prompt_window)
5881             clrscr()
5882             proutn("COMMAND> ")
5883             if scanner.next() == "IHEOL":
5884                 if game.options & OPTION_CURSES:
5885                     makechart()
5886                 continue
5887             elif scanner.token == "":
5888                 continue
5889             game.ididit = False
5890             clrscr()
5891             setwnd(message_window)
5892             clrscr()
5893             candidates = filter(lambda x: x.startswith(scanner.token.upper()),
5894                                 commands)
5895             if len(candidates) == 1:
5896                 cmd = candidates[0]
5897                 break
5898             elif candidates and not (game.options & OPTION_PLAIN):
5899                 prout("Commands with prefix '%s': %s" % (scanner.token, " ".join(candidates)))
5900             else:
5901                 listCommands()
5902                 continue
5903         if cmd == "SRSCAN":             # srscan
5904             srscan()
5905         elif cmd == "STATUS":           # status
5906             status()
5907         elif cmd == "REQUEST":          # status request 
5908             request()
5909         elif cmd == "LRSCAN":           # long range scan
5910             lrscan(silent=False)
5911         elif cmd == "PHASERS":          # phasers
5912             phasers()
5913             if game.ididit:
5914                 hitme = True
5915         elif cmd in ("TORPEDO", "PHOTONS"):     # photon torpedos
5916             torps()
5917             if game.ididit:
5918                 hitme = True
5919         elif cmd == "MOVE":             # move under warp
5920             warp(wcourse=None, involuntary=False)
5921         elif cmd == "SHIELDS":          # shields
5922             doshield(shraise=False)
5923             if game.ididit:
5924                 hitme = True
5925                 game.shldchg = False
5926         elif cmd == "DOCK":             # dock at starbase
5927             dock(True)
5928             if game.ididit:
5929                 attack(torps_ok=False)          
5930         elif cmd == "DAMAGES":          # damage reports
5931             damagereport()
5932         elif cmd == "CHART":            # chart
5933             makechart()
5934         elif cmd == "IMPULSE":          # impulse
5935             impulse()
5936         elif cmd == "REST":             # rest
5937             wait()
5938             if game.ididit:
5939                 hitme = True
5940         elif cmd == "WARP":             # warp
5941             setwarp()
5942         elif cmd == "SCORE":            # score
5943             score()
5944         elif cmd == "SENSORS":          # sensors
5945             sensor()
5946         elif cmd == "ORBIT":            # orbit
5947             orbit()
5948             if game.ididit:
5949                 hitme = True
5950         elif cmd == "TRANSPORT":                # transport "beam"
5951             beam()
5952         elif cmd == "MINE":             # mine
5953             mine()
5954             if game.ididit:
5955                 hitme = True
5956         elif cmd == "CRYSTALS":         # crystals
5957             usecrystals()
5958             if game.ididit:
5959                 hitme = True
5960         elif cmd == "SHUTTLE":          # shuttle
5961             shuttle()
5962             if game.ididit:
5963                 hitme = True
5964         elif cmd == "PLANETS":          # Planet list
5965             survey()
5966         elif cmd == "REPORT":           # Game Report 
5967             report()
5968         elif cmd == "COMPUTER":         # use COMPUTER!
5969             eta()
5970         elif cmd == "COMMANDS":
5971             listCommands()
5972         elif cmd == "EMEXIT":           # Emergency exit
5973             clrscr()                    # Hide screen
5974             freeze(True)                # forced save
5975             raise SystemExit,1          # And quick exit
5976         elif cmd == "PROBE":
5977             probe()                     # Launch probe
5978             if game.ididit:
5979                 hitme = True
5980         elif cmd == "ABANDON":          # Abandon Ship
5981             abandon()
5982         elif cmd == "DESTRUCT":         # Self Destruct
5983             selfdestruct()
5984         elif cmd == "SAVE":             # Save Game
5985             freeze(False)
5986             clrscr()
5987             if game.skill > SKILL_GOOD:
5988                 prout(_("WARNING--Saved games produce no plaques!"))
5989         elif cmd == "DEATHRAY":         # Try a desparation measure
5990             deathray()
5991             if game.ididit:
5992                 hitme = True
5993         elif cmd == "DEBUGCMD":         # What do we want for debug???
5994             debugme()
5995         elif cmd == "MAYDAY":           # Call for help
5996             mayday()
5997             if game.ididit:
5998                 hitme = True
5999         elif cmd == "QUIT":
6000             game.alldone = True         # quit the game
6001         elif cmd == "HELP":
6002             helpme()                    # get help
6003         while True:
6004             if game.alldone:
6005                 break           # Game has ended
6006             if game.optime != 0.0:
6007                 events()
6008                 if game.alldone:
6009                     break       # Events did us in
6010             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6011                 atover(False)
6012                 continue
6013             if hitme and not game.justin:
6014                 attack(torps_ok=True)
6015                 if game.alldone:
6016                     break
6017                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6018                     atover(False)
6019                     hitme = True
6020                     continue
6021             break
6022         if game.alldone:
6023             break
6024     if game.idebug:
6025         prout("=== Ending")
6026
6027 def cramen(type):
6028     "Emit the name of an enemy or feature." 
6029     if   type == 'R': s = _("Romulan")
6030     elif type == 'K': s = _("Klingon")
6031     elif type == 'C': s = _("Commander")
6032     elif type == 'S': s = _("Super-commander")
6033     elif type == '*': s = _("Star")
6034     elif type == 'P': s = _("Planet")
6035     elif type == 'B': s = _("Starbase")
6036     elif type == ' ': s = _("Black hole")
6037     elif type == 'T': s = _("Tholian")
6038     elif type == '#': s = _("Tholian web")
6039     elif type == '?': s = _("Stranger")
6040     elif type == '@': s = _("Inhabited World")
6041     else: s = "Unknown??"
6042     return s
6043
6044 def crmena(stars, enemy, loctype, w):
6045     "Emit the name of an enemy and his location."
6046     buf = ""
6047     if stars:
6048         buf += "***"
6049     buf += cramen(enemy) + _(" at ")
6050     if loctype == "quadrant":
6051         buf += _("Quadrant ")
6052     elif loctype == "sector":
6053         buf += _("Sector ")
6054     return buf + `w`
6055
6056 def crmshp():
6057     "Emit our ship name." 
6058     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6059
6060 def stars():
6061     "Emit a line of stars" 
6062     prouts("******************************************************")
6063     skip(1)
6064
6065 def expran(avrage):
6066     return -avrage*math.log(1e-7 + randreal())
6067
6068 def randplace(size):
6069     "Choose a random location."
6070     w = Coord()
6071     w.i = randrange(size) 
6072     w.j = randrange(size)
6073     return w
6074
6075 class sstscanner:
6076     def __init__(self):
6077         self.type = None
6078         self.token = None
6079         self.real = 0.0
6080         self.inqueue = []
6081     def next(self):
6082         # Get a token from the user
6083         self.real = 0.0
6084         self.token = ''
6085         # Fill the token quue if nothing here
6086         while not self.inqueue:
6087             line = cgetline()
6088             if curwnd==prompt_window:
6089                 clrscr()
6090                 setwnd(message_window)
6091                 clrscr()
6092             if line == '':
6093                 return None
6094             if not line:
6095                 continue
6096             else:
6097                 self.inqueue = line.lstrip().split() + ["\n"]
6098         # From here on in it's all looking at the queue
6099         self.token = self.inqueue.pop(0)
6100         if self.token == "\n":
6101             self.type = "IHEOL"
6102             return "IHEOL"
6103         try:
6104             self.real = float(self.token)
6105             self.type = "IHREAL"
6106             return "IHREAL"
6107         except ValueError:
6108             pass
6109         # Treat as alpha
6110         self.token = self.token.lower()
6111         self.type = "IHALPHA"
6112         self.real = None
6113         return "IHALPHA"
6114     def append(self, tok):
6115         self.inqueue.append(tok)
6116     def push(self, tok):
6117         self.inqueue.insert(0, tok)
6118     def waiting(self):
6119         return self.inqueue
6120     def chew(self):
6121         # Demand input for next scan
6122         self.inqueue = []
6123         self.real = self.token = None
6124     def sees(self, s):
6125         # compares s to item and returns true if it matches to the length of s
6126         return s.startswith(self.token)
6127     def int(self):
6128         # Round token value to nearest integer
6129         return int(round(scanner.real))
6130     def getcoord(self):
6131         s = Coord()
6132         scanner.next()
6133         if scanner.type != "IHREAL":
6134             huh()
6135             return None
6136         s.i = scanner.int()-1
6137         scanner.next()
6138         if scanner.type != "IHREAL":
6139             huh()
6140             return None
6141         s.j = scanner.int()-1
6142         return s
6143     def __repr__(self):
6144         return "<sstcanner: token=%s, type=%s, queue=%s>" % (scanner.token, scanner.type, scanner.inqueue)
6145
6146 def ja():
6147     "Yes-or-no confirmation."
6148     scanner.chew()
6149     while True:
6150         scanner.next()
6151         if scanner.token == 'y':
6152             return True
6153         if scanner.token == 'n':
6154             return False
6155         scanner.chew()
6156         proutn(_("Please answer with \"y\" or \"n\": "))
6157
6158 def huh():
6159     "Complain about unparseable input."
6160     scanner.chew()
6161     skip(1)
6162     prout(_("Beg your pardon, Captain?"))
6163
6164 def debugme():
6165     "Access to the internals for debugging."
6166     proutn("Reset levels? ")
6167     if ja():
6168         if game.energy < game.inenrg:
6169             game.energy = game.inenrg
6170         game.shield = game.inshld
6171         game.torps = game.intorps
6172         game.lsupres = game.inlsr
6173     proutn("Reset damage? ")
6174     if ja():
6175         for i in range(NDEVICES): 
6176             if game.damage[i] > 0.0: 
6177                 game.damage[i] = 0.0
6178     proutn("Toggle debug flag? ")
6179     if ja():
6180         game.idebug = not game.idebug
6181         if game.idebug:
6182             prout("Debug output ON")        
6183         else:
6184             prout("Debug output OFF")
6185     proutn("Cause selective damage? ")
6186     if ja():
6187         for i in range(NDEVICES):
6188             proutn("Kill %s?" % device[i])
6189             scanner.chew()
6190             key = scanner.next()
6191             if key == "IHALPHA" and scanner.sees("y"):
6192                 game.damage[i] = 10.0
6193     proutn("Examine/change events? ")
6194     if ja():
6195         ev = Event()
6196         w = Coord()
6197         legends = {
6198             FSNOVA:  "Supernova       ",
6199             FTBEAM:  "T Beam          ",
6200             FSNAP:   "Snapshot        ",
6201             FBATTAK: "Base Attack     ",
6202             FCDBAS:  "Base Destroy    ",
6203             FSCMOVE: "SC Move         ",
6204             FSCDBAS: "SC Base Destroy ",
6205             FDSPROB: "Probe Move      ",
6206             FDISTR:  "Distress Call   ",
6207             FENSLV:  "Enslavement     ",
6208             FREPRO:  "Klingon Build   ",
6209         }
6210         for i in range(1, NEVENTS):
6211             proutn(legends[i])
6212             if is_scheduled(i):
6213                 proutn("%.2f" % (scheduled(i)-game.state.date))
6214                 if i == FENSLV or i == FREPRO:
6215                     ev = findevent(i)
6216                     proutn(" in %s" % ev.quadrant)
6217             else:
6218                 proutn("never")
6219             proutn("? ")
6220             scanner.chew()
6221             key = scanner.next()
6222             if key == 'n':
6223                 unschedule(i)
6224                 scanner.chew()
6225             elif key == "IHREAL":
6226                 ev = schedule(i, scanner.real)
6227                 if i == FENSLV or i == FREPRO:
6228                     scanner.chew()
6229                     proutn("In quadrant- ")
6230                     key = scanner.next()
6231                     # "IHEOL" says to leave coordinates as they are 
6232                     if key != "IHEOL":
6233                         if key != "IHREAL":
6234                             prout("Event %d canceled, no x coordinate." % (i))
6235                             unschedule(i)
6236                             continue
6237                         w.i = int(round(scanner.real))
6238                         key = scanner.next()
6239                         if key != "IHREAL":
6240                             prout("Event %d canceled, no y coordinate." % (i))
6241                             unschedule(i)
6242                             continue
6243                         w.j = int(round(scanner.real))
6244                         ev.quadrant = w
6245         scanner.chew()
6246     proutn("Induce supernova here? ")
6247     if ja():
6248         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6249         atover(True)
6250
6251 if __name__ == '__main__':
6252     import getopt, socket
6253     try:
6254         global line, thing, game
6255         game = None
6256         thing = Coord()
6257         thing.angry = False
6258         game = Gamestate()
6259         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6260         if os.getenv("TERM"):
6261             game.options |= OPTION_CURSES
6262         else:
6263             game.options |= OPTION_TTY
6264         seed = int(time.time())
6265         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:txV")
6266         for (switch, val) in options:
6267             if switch == '-r':
6268                 try:
6269                     replayfp = open(val, "r")
6270                 except IOError:
6271                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6272                     raise SystemExit, 1
6273                 try:
6274                     line = replayfp.readline().strip()
6275                     (leader, key, seed) = line.split()
6276                     seed = eval(seed)
6277                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6278                     line = replayfp.readline().strip()
6279                     arguments += line.split()[2:]
6280                 except ValueError:
6281                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6282                     raise SystemExit(1)
6283                 game.options |= OPTION_TTY
6284                 game.options &=~ OPTION_CURSES
6285             elif switch == '-s':
6286                 seed = int(val)
6287             elif switch == '-t':
6288                 game.options |= OPTION_TTY
6289                 game.options &=~ OPTION_CURSES
6290             elif switch == '-x':
6291                 game.idebug = True
6292             elif switch == '-V':
6293                 print "SST2K", version
6294                 raise SystemExit, 0 
6295             else:
6296                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6297                 raise SystemExit, 1
6298         # where to save the input in case of bugs
6299         if "TMPDIR" in os.environ:
6300             tmpdir = os.environ['TMPDIR']
6301         else:
6302             tmpdir = "/tmp"
6303         try:
6304             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6305         except IOError:
6306             sys.stderr.write("sst: warning, can't open logfile\n")
6307             sys.exit(1)
6308         if logfp:
6309             logfp.write("# seed %s\n" % seed)
6310             logfp.write("# options %s\n" % " ".join(arguments))
6311             logfp.write("# recorded by %s@%s on %s\n" % \
6312                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6313         random.seed(seed)
6314         scanner = sstscanner()
6315         map(scanner.append, arguments)
6316         try:
6317             iostart()
6318             while True: # Play a game 
6319                 setwnd(fullscreen_window)
6320                 clrscr()
6321                 prelim()
6322                 setup()
6323                 if game.alldone:
6324                     score()
6325                     game.alldone = False
6326                 else:
6327                     makemoves()
6328                 skip(1)
6329                 stars()
6330                 skip(1)
6331                 if game.tourn and game.alldone:
6332                     proutn(_("Do you want your score recorded?"))
6333                     if ja():
6334                         scanner.chew()
6335                         scanner.push("\n")
6336                         freeze(False)
6337                 scanner.chew()
6338                 proutn(_("Do you want to play again? "))
6339                 if not ja():
6340                     break
6341             skip(1)
6342             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6343         finally:
6344             ioend()
6345         raise SystemExit, 0
6346     except KeyboardInterrupt:
6347         if logfp:
6348             logfp.close()
6349         print ""