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