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