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