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