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