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