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