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