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