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