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