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