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