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