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