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