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