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