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