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