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