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