Typo fix.
[super-star-trek.git] / sst.py
1 #!/usr/bin/env python
2 """
3 sst.py -- Super Star Trek 2K
4
5 SST2K is a Python translation of a C translation of a FORTRAN
6 original dating back to 1973.  Beautiful Python it is not, but it
7 works.  Translation by Eric S. Raymond; original game by David Matuszek
8 and Paul Reynolds, with modifications by Don Smith, Tom Almy,
9 Stas Sergeev, and Eric S. Raymond.
10
11 See the doc/HACKING file in the distribution for designers notes and advice
12 on how to modify (and how not to modify!) this code.
13 """
14 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.5"
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 OPTION_DOTFILL    = 0x08000000        # fix dotfill glitch in chart (ESR, 2019)
313
314 # Define devices
315 DSRSENS         = 0
316 DLRSENS         = 1
317 DPHASER         = 2
318 DPHOTON         = 3
319 DLIFSUP         = 4
320 DWARPEN         = 5
321 DIMPULS         = 6
322 DSHIELD         = 7
323 DRADIO          = 8
324 DSHUTTL         = 9
325 DCOMPTR         = 10
326 DNAVSYS         = 11
327 DTRANSP         = 12
328 DSHCTRL         = 13
329 DDRAY           = 14
330 DDSP            = 15
331 DCLOAK          = 16
332 NDEVICES        = 17        # Number of devices
333
334 SKILL_NONE      = 0
335 SKILL_NOVICE    = 1
336 SKILL_FAIR      = 2
337 SKILL_GOOD      = 3
338 SKILL_EXPERT    = 4
339 SKILL_EMERITUS  = 5
340
341 def damaged(dev):
342     return (game.damage[dev] != 0.0)
343 def communicating():
344     return not damaged(DRADIO) or game.condition=="docked"
345
346 # Define future events
347 FSPY    = 0        # Spy event happens always (no future[] entry)
348                    # can cause SC to tractor beam Enterprise
349 FSNOVA  = 1        # Supernova
350 FTBEAM  = 2        # Commander tractor beams Enterprise
351 FSNAP   = 3        # Snapshot for time warp
352 FBATTAK = 4        # Commander attacks base
353 FCDBAS  = 5        # Commander destroys base
354 FSCMOVE = 6        # Supercommander moves (might attack base)
355 FSCDBAS = 7        # Supercommander destroys base
356 FDSPROB = 8        # Move deep space probe
357 FDISTR  = 9        # Emit distress call from an inhabited world
358 FENSLV  = 10       # Inhabited word is enslaved
359 FREPRO  = 11       # Klingons build a ship in an enslaved system
360 NEVENTS = 12
361
362 # Abstract out the event handling -- underlying data structures will change
363 # when we implement stateful events
364 def findevent(evtype):
365     return game.future[evtype]
366
367 class Enemy:
368     def __init__(self, etype=None, loc=None, power=None):
369         self.type = etype
370         self.location = Coord()
371         self.kdist = None
372         self.kavgd = None
373         if loc:
374             self.move(loc)
375         self.power = power        # enemy energy level
376         game.enemies.append(self)
377     def move(self, loc):
378         motion = (loc != self.location)
379         if self.location.i is not None and self.location.j is not None:
380             if motion:
381                 if self.type == 'T':
382                     game.quad[self.location.i][self.location.j] = '#'
383                 else:
384                     game.quad[self.location.i][self.location.j] = '.'
385         if loc:
386             self.location = copy.copy(loc)
387             game.quad[self.location.i][self.location.j] = self.type
388             self.kdist = self.kavgd = (game.sector - loc).distance()
389         else:
390             self.location = Coord()
391             self.kdist = self.kavgd = None
392             # Guard prevents failure on Tholian or thingy
393             if self in game.enemies:
394                 game.enemies.remove(self)
395         return motion
396     def __repr__(self):
397         return "<%s,%s.%f>" % (self.type, self.location, self.power)        # For debugging
398
399 class Gamestate:
400     def __init__(self):
401         self.options = None        # Game options
402         self.state = Snapshot()        # A snapshot structure
403         self.snapsht = Snapshot()        # Last snapshot taken for time-travel purposes
404         self.quad = None        # contents of our quadrant
405         self.damage = [0.0] * NDEVICES        # damage encountered
406         self.future = []        # future events
407         i = NEVENTS
408         while i > 0:
409             i -= 1
410             self.future.append(Event())
411         self.passwd  = None        # Self Destruct password
412         self.enemies = []
413         self.quadrant = None        # where we are in the large
414         self.sector = None        # where we are in the small
415         self.tholian = None        # Tholian enemy object
416         self.base = None        # position of base in current quadrant
417         self.battle = None        # base coordinates being attacked
418         self.plnet = None        # location of planet in quadrant
419         self.gamewon = False        # Finished!
420         self.ididit = False        # action taken -- allows enemy to attack
421         self.alive = False        # we are alive (not killed)
422         self.justin = False        # just entered quadrant
423         self.shldup = False        # shields are up
424         self.shldchg = False        # shield is changing (affects efficiency)
425         self.iscate = False        # super commander is here
426         self.ientesc = False        # attempted escape from supercommander
427         self.resting = False        # rest time
428         self.icraft = False        # Kirk in Galileo
429         self.landed = False        # party on planet (true), on ship (false)
430         self.alldone = False        # game is now finished
431         self.neutz = False        # Romulan Neutral Zone
432         self.isarmed = False        # probe is armed
433         self.inorbit = False        # orbiting a planet
434         self.imine = False        # mining
435         self.icrystl = False        # dilithium crystals aboard
436         self.iseenit = False        # seen base attack report
437         self.thawed = False        # thawed game
438         self.condition = None        # "green", "yellow", "red", "docked", "dead"
439         self.iscraft = None        # "onship", "offship", "removed"
440         self.skill = SKILL_NONE        # Player skill level
441         self.inkling = 0        # initial number of klingons
442         self.inbase = 0                # initial number of bases
443         self.incom = 0                # initial number of commanders
444         self.inscom = 0                # initial number of commanders
445         self.inrom = 0                # initial number of commanders
446         self.instar = 0                # initial stars
447         self.intorps = 0        # initial/max torpedoes
448         self.torps = 0                # number of torpedoes
449         self.ship = 0                # ship type -- 'E' is Enterprise
450         self.abandoned = 0        # count of crew abandoned in space
451         self.length = 0                # length of game
452         self.klhere = 0                # klingons here
453         self.casual = 0                # causalties
454         self.nhelp = 0                # calls for help
455         self.nkinks = 0                # count of energy-barrier crossings
456         self.iplnet = None        # planet # in quadrant
457         self.inplan = 0                # initial planets
458         self.irhere = 0                # Romulans in quadrant
459         self.isatb = 0                # =2 if super commander is attacking base
460         self.tourn = None        # tournament number
461         self.nprobes = 0        # number of probes available
462         self.inresor = 0.0        # initial resources
463         self.intime = 0.0        # initial time
464         self.inenrg = 0.0        # initial/max energy
465         self.inshld = 0.0        # initial/max shield
466         self.inlsr = 0.0        # initial life support resources
467         self.indate = 0.0        # initial date
468         self.energy = 0.0        # energy level
469         self.shield = 0.0        # shield level
470         self.warpfac = 0.0        # warp speed
471         self.lsupres = 0.0        # life support reserves
472         self.optime = 0.0        # time taken by current operation
473         self.damfac = 0.0        # damage factor
474         self.lastchart = 0.0        # time star chart was last updated
475         self.cryprob = 0.0        # probability that crystal will work
476         self.probe = None        # object holding probe course info
477         self.height = 0.0        # height of orbit around planet
478         self.score = 0.0        # overall score
479         self.perdate = 0.0        # rate of kills
480         self.idebug = False        # Debugging instrumentation enabled?
481         self.cdebug = False        # Debugging instrumentation for curses enabled?
482         self.statekscmdr = None # No SuperCommander coordinates yet.
483         self.brigcapacity = 400     # Enterprise brig capacity
484         self.brigfree = 400       # How many klingons can we put in the brig?
485         self.kcaptured = 0      # Total Klingons captured, for scoring.
486         self.iscloaked = False  # Cloaking device on?
487         self.ncviol = 0         # Algreon treaty violations
488         self.isviolreported = False # We have been warned
489         self.lcg_x = 0          # LCG generator value
490     def remkl(self):
491         return sum([q.klingons for (_i, _j, q) in list(self.state.traverse())])
492     def recompute(self):
493         # Stas thinks this should be (C expression):
494         # game.remkl() + len(game.state.kcmdr) > 0 ?
495         #        game.state.remres/(game.remkl() + 4*len(game.state.kcmdr)) : 99
496         # He says the existing expression is prone to divide-by-zero errors
497         # after killing the last klingon when score is shown -- perhaps also
498         # if the only remaining klingon is SCOM.
499         self.state.remtime = self.state.remres/(self.remkl() + 4*len(self.state.kcmdr))
500     def unwon(self):
501         "Are there Klingons remaining?"
502         return self.remkl()
503
504 FWON = 0
505 FDEPLETE = 1
506 FLIFESUP = 2
507 FNRG = 3
508 FBATTLE = 4
509 FNEG3 = 5
510 FNOVA = 6
511 FSNOVAED = 7
512 FABANDN = 8
513 FDILITHIUM = 9
514 FMATERIALIZE = 10
515 FPHASER = 11
516 FLOST = 12
517 FMINING = 13
518 FDPLANET = 14
519 FPNOVA = 15
520 FSSC = 16
521 FSTRACTOR = 17
522 FDRAY = 18
523 FTRIBBLE = 19
524 FHOLE = 20
525 FCREW = 21
526 FCLOAK = 22
527
528 # Code from ai.c begins here
529
530 def welcoming(iq):
531     "Would this quadrant welcome another Klingon?"
532     return iq.valid_quadrant() and \
533         not game.state.galaxy[iq.i][iq.j].supernova and \
534         game.state.galaxy[iq.i][iq.j].klingons < MAXKLQUAD
535
536 def tryexit(enemy, look, irun):
537     "A bad guy attempts to bug out."
538     iq = Coord()
539     iq.i = game.quadrant.i+(look.i+(QUADSIZE-1))//QUADSIZE - 1
540     iq.j = game.quadrant.j+(look.j+(QUADSIZE-1))//QUADSIZE - 1
541     if not welcoming(iq):
542         return False
543     if enemy.type == 'R':
544         return False # Romulans cannot escape!
545     if not irun:
546         # avoid intruding on another commander's territory
547         if enemy.type == 'C':
548             if iq in game.state.kcmdr:
549                 return []
550             # refuse to leave if currently attacking starbase
551             if game.battle == game.quadrant:
552                 return []
553         # don't leave if over 1000 units of energy
554         if enemy.power > 1000.0:
555             return []
556     oldloc = copy.copy(enemy.location)
557     # handle local matters related to escape
558     enemy.move(None)
559     game.klhere -= 1
560     if game.condition != "docked":
561         newcnd()
562     # Handle global matters related to escape
563     game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons -= 1
564     game.state.galaxy[iq.i][iq.j].klingons += 1
565     if enemy.type == 'S':
566         game.iscate = False
567         game.ientesc = False
568         game.isatb = 0
569         schedule(FSCMOVE, 0.2777)
570         unschedule(FSCDBAS)
571         game.state.kscmdr = iq
572     else:
573         for cmdr in game.state.kcmdr:
574             if cmdr == game.quadrant:
575                 game.state.kcmdr.append(iq)
576                 break
577     # report move out of quadrant.
578     return [(True, enemy, oldloc, iq)]
579
580 # The bad-guy movement algorithm:
581 #
582 # 1. Enterprise has "force" based on condition of phaser and photon torpedoes.
583 # If both are operating full strength, force is 1000. If both are damaged,
584 # force is -1000. Having shields down subtracts an additional 1000.
585 #
586 # 2. Enemy has forces equal to the energy of the attacker plus
587 # 100*(K+R) + 500*(C+S) - 400 for novice through good levels OR
588 # 346*K + 400*R + 500*(C+S) - 400 for expert and emeritus.
589 #
590 # Attacker Initial energy levels (nominal):
591 # Klingon   Romulan   Commander   Super-Commander
592 # Novice    400        700        1200
593 # Fair      425        750        1250
594 # Good      450        800        1300        1750
595 # Expert    475        850        1350        1875
596 # Emeritus  500        900        1400        2000
597 # VARIANCE   75        200         200         200
598 #
599 # Enemy vessels only move prior to their attack. In Novice - Good games
600 # only commanders move. In Expert games, all enemy vessels move if there
601 # is a commander present. In Emeritus games all enemy vessels move.
602 #
603 # 3. If Enterprise is not docked, an aggressive action is taken if enemy
604 # forces are 1000 greater than Enterprise.
605 #
606 # Agressive action on average cuts the distance between the ship and
607 # the enemy to 1/4 the original.
608 #
609 # 4.  At lower energy advantage, movement units are proportional to the
610 # advantage with a 650 advantage being to hold ground, 800 to move forward
611 # 1, 950 for two, 150 for back 4, etc. Variance of 100.
612 #
613 # If docked, is reduced by roughly 1.75*game.skill, generally forcing a
614 # retreat, especially at high skill levels.
615 #
616 # 5.  Motion is limited to skill level, except for SC hi-tailing it out.
617
618 def movebaddy(enemy):
619     "Tactical movement for the bad guys."
620     goto = Coord()
621     look = Coord()
622     irun = False
623     # This should probably be just (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
624     if game.skill >= SKILL_EXPERT:
625         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)
626     else:
627         nbaddys = (game.quadrant in game.state.kcmdr) + (game.state.kscmdr==game.quadrant)
628     old_dist = enemy.kdist
629     mdist = int(old_dist + 0.5) # Nearest integer distance
630     # If SC, check with spy to see if should hi-tail it
631     if enemy.type == 'S' and \
632         (enemy.power <= 500.0 or (game.condition=="docked" and not damaged(DPHOTON))):
633         irun = True
634         motion = -QUADSIZE
635     else:
636         # decide whether to advance, retreat, or hold position
637         forces = enemy.power+100.0*len(game.enemies)+400*(nbaddys-1)
638         if not game.shldup:
639             forces += 1000 # Good for enemy if shield is down!
640         if not damaged(DPHASER) or not damaged(DPHOTON):
641             if damaged(DPHASER): # phasers damaged
642                 forces += 300.0
643             else:
644                 forces -= 0.2*(game.energy - 2500.0)
645             if damaged(DPHOTON): # photon torpedoes damaged
646                 forces += 300.0
647             else:
648                 forces -= 50.0*game.torps
649         else:
650             # phasers and photon tubes both out!
651             forces += 1000.0
652         motion = 0
653         if forces <= 1000.0 and game.condition != "docked": # Typical situation
654             motion = ((forces + rnd.real(200))/150.0) - 5.0
655         else:
656             if forces > 1000.0: # Very strong -- move in for kill
657                 motion = (1.0 - rnd.real())**2 * old_dist + 1.0
658             if game.condition == "docked" and (game.options & OPTION_BASE): # protected by base -- back off !
659                 motion -= game.skill*(2.0-rnd.real()**2)
660         if game.idebug:
661             proutn("=== MOTION = %d, FORCES = %1.2f, " % (motion, forces))
662         # don't move if no motion
663         if motion == 0:
664             return []
665         # Limit motion according to skill
666         if abs(motion) > game.skill:
667             if motion < 0:
668                 motion = -game.skill
669             else:
670                 motion = game.skill
671     # calculate preferred number of steps
672     nsteps = abs(int(motion))
673     if motion > 0 and nsteps > mdist:
674         nsteps = mdist # don't overshoot
675     if nsteps > QUADSIZE:
676         nsteps = QUADSIZE # This shouldn't be necessary
677     if nsteps < 1:
678         nsteps = 1 # This shouldn't be necessary
679     if game.idebug:
680         proutn("NSTEPS = %d:" % nsteps)
681     # Compute preferred values of delta X and Y
682     m = game.sector - enemy.location
683     if 2.0 * abs(m.i) < abs(m.j):
684         m.i = 0
685     if 2.0 * abs(m.j) < abs(game.sector.i-enemy.location.i):
686         m.j = 0
687     m = (motion * m).sgn()
688     goto = enemy.location
689     # main move loop
690     for ll in range(nsteps):
691         if game.idebug:
692             proutn(" %d" % (ll+1))
693         # Check if preferred position available
694         look = goto + m
695         if m.i < 0:
696             krawli = 1
697         else:
698             krawli = -1
699         if m.j < 0:
700             krawlj = 1
701         else:
702             krawlj = -1
703         success = False
704         attempts = 0 # Settle mysterious hang problem
705         while attempts < 20 and not success:
706             attempts += 1
707             if look.i < 0 or look.i >= QUADSIZE:
708                 if motion < 0:
709                     return tryexit(enemy, look, irun)
710                 if krawli == m.i or m.j == 0:
711                     break
712                 look.i = goto.i + krawli
713                 krawli = -krawli
714             elif look.j < 0 or look.j >= QUADSIZE:
715                 if motion < 0:
716                     return tryexit(enemy, look, irun)
717                 if krawlj == m.j or m.i == 0:
718                     break
719                 look.j = goto.j + krawlj
720                 krawlj = -krawlj
721             elif (game.options & OPTION_RAMMING) and game.quad[look.i][look.j] != '.':
722                 # See if enemy should ram ship
723                 if game.quad[look.i][look.j] == game.ship and \
724                     (enemy.type == 'C' or enemy.type == 'S'):
725                     collision(rammed=True, enemy=enemy)
726                     return []
727                 if krawli != m.i and m.j != 0:
728                     look.i = goto.i + krawli
729                     krawli = -krawli
730                 elif krawlj != m.j and m.i != 0:
731                     look.j = goto.j + krawlj
732                     krawlj = -krawlj
733                 else:
734                     break # we have failed
735             else:
736                 success = True
737         if success:
738             goto = look
739             if game.idebug:
740                 proutn(repr(goto))
741         else:
742             break # done early
743     if game.idebug:
744         skip(1)
745     # Enemy moved, but is still in sector
746     return [(False, enemy, old_dist, goto)]
747
748 def moveklings():
749     "Sequence Klingon tactical movement."
750     if game.idebug:
751         prout("== MOVCOM")
752     # Figure out which Klingon is the commander (or Supercommander)
753     # and do move
754     tacmoves = []
755     if game.quadrant in game.state.kcmdr:
756         for enemy in game.enemies:
757             if enemy.type == 'C':
758                 tacmoves += movebaddy(enemy)
759     if game.state.kscmdr == game.quadrant:
760         for enemy in game.enemies:
761             if enemy.type == 'S':
762                 tacmoves += movebaddy(enemy)
763                 break
764     # If skill level is high, move other Klingons and Romulans too!
765     # Move these last so they can base their actions on what the
766     # commander(s) do.
767     if game.skill >= SKILL_EXPERT and (game.options & OPTION_MVBADDY):
768         for enemy in game.enemies:
769             if enemy.type in ('K', 'R'):
770                 tacmoves += movebaddy(enemy)
771     return tacmoves
772
773 def movescom(iq, avoid):
774     "Supercommander movement helper."
775     # Avoid quadrants with bases if we want to avoid Enterprise
776     if not welcoming(iq) or (avoid and iq in game.state.baseq):
777         return False
778     if game.justin and not game.iscate:
779         return False
780     # do the move
781     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons -= 1
782     game.state.kscmdr = iq
783     game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].klingons += 1
784     if game.state.kscmdr == game.quadrant:
785         # SC has scooted, remove him from current quadrant
786         game.iscate = False
787         game.isatb = 0
788         game.ientesc = False
789         unschedule(FSCDBAS)
790         for enemy in game.enemies:
791             if enemy.type == 'S':
792                 enemy.move(None)
793         game.klhere -= 1
794         if game.condition != "docked":
795             newcnd()
796         sortenemies()
797     # check for a helpful planet
798     for i in range(game.inplan):
799         if game.state.planets[i].quadrant == game.state.kscmdr and \
800             game.state.planets[i].crystals == "present":
801             # destroy the planet
802             game.state.planets[i].pclass = "destroyed"
803             game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].planet = None
804             if communicating():
805                 announce()
806                 prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
807                 prout(_("   a planet in Quadrant %s has been destroyed") % game.state.kscmdr)
808                 prout(_("   by the Super-commander.\""))
809             break
810     return True # looks good!
811
812 def supercommander():
813     "Move the Super Commander."
814     iq = Coord()
815     sc = Coord()
816     ibq = Coord()
817     idelta = Coord()
818     basetbl = []
819     if game.idebug:
820         prout("== SUPERCOMMANDER")
821     # Decide on being active or passive
822     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 \
823             (game.state.date-game.indate) < 3.0)
824     if not game.iscate and avoid:
825         # compute move away from Enterprise
826         idelta = game.state.kscmdr-game.quadrant
827         if idelta.distance() > 2.0:
828             # circulate in space
829             idelta.i = game.state.kscmdr.j-game.quadrant.j
830             idelta.j = game.quadrant.i-game.state.kscmdr.i
831     else:
832         # compute distances to starbases
833         if not game.state.baseq:
834             # nothing left to do
835             unschedule(FSCMOVE)
836             return
837         sc = game.state.kscmdr
838         for (i, base) in enumerate(game.state.baseq):
839             basetbl.append((i, (base - sc).distance()))
840         if len(game.state.baseq) > 1:
841             basetbl.sort(key=lambda x: x[1])
842         # look for nearest base without a commander, no Enterprise, and
843         # without too many Klingons, and not already under attack.
844         ifindit = iwhichb = 0
845         for (i2, base) in enumerate(game.state.baseq):
846             i = basetbl[i2][0]        # bug in original had it not finding nearest
847             if base == game.quadrant or base == game.battle or not welcoming(base):
848                 continue
849             # if there is a commander, and no other base is appropriate,
850             # we will take the one with the commander
851             for cmdr in game.state.kcmdr:
852                 if base == cmdr and ifindit != 2:
853                     ifindit = 2
854                     iwhichb = i
855                     break
856             else:        # no commander -- use this one
857                 ifindit = 1
858                 iwhichb = i
859                 break
860         if ifindit == 0:
861             return # Nothing suitable -- wait until next time
862         ibq = game.state.baseq[iwhichb]
863         # decide how to move toward base
864         idelta = ibq - game.state.kscmdr
865     # Maximum movement is 1 quadrant in either or both axes
866     idelta = idelta.sgn()
867     # try moving in both x and y directions
868     # there was what looked like a bug in the Almy C code here,
869     # but it might be this translation is just wrong.
870     iq = game.state.kscmdr + idelta
871     if not movescom(iq, avoid):
872         # failed -- try some other maneuvers
873         if idelta.i == 0 or idelta.j == 0:
874             # attempt angle move
875             if idelta.i != 0:
876                 iq.j = game.state.kscmdr.j + 1
877                 if not movescom(iq, avoid):
878                     iq.j = game.state.kscmdr.j - 1
879                     movescom(iq, avoid)
880             elif idelta.j != 0:
881                 iq.i = game.state.kscmdr.i + 1
882                 if not movescom(iq, avoid):
883                     iq.i = game.state.kscmdr.i - 1
884                     movescom(iq, avoid)
885         else:
886             # try moving just in x or y
887             iq.j = game.state.kscmdr.j
888             if not movescom(iq, avoid):
889                 iq.j = game.state.kscmdr.j + idelta.j
890                 iq.i = game.state.kscmdr.i
891                 movescom(iq, avoid)
892     # check for a base
893     if len(game.state.baseq) == 0:
894         unschedule(FSCMOVE)
895     else:
896         for ibq in game.state.baseq:
897             if ibq == game.state.kscmdr and game.state.kscmdr == game.battle:
898                 # attack the base
899                 if avoid:
900                     return # no, don't attack base!
901                 game.iseenit = False
902                 game.isatb = 1
903                 schedule(FSCDBAS, rnd.real(1.0, 3.0))
904                 if is_scheduled(FCDBAS):
905                     postpone(FSCDBAS, scheduled(FCDBAS)-game.state.date)
906                 if not communicating():
907                     return # no warning
908                 game.iseenit = True
909                 announce()
910                 prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") \
911                       % game.state.kscmdr)
912                 prout(_("   reports that it is under attack from the Klingon Super-commander."))
913                 prout(_("   It can survive until stardate %d.\"") \
914                        % int(scheduled(FSCDBAS)))
915                 if not game.resting:
916                     return
917                 prout(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
918                 if not ja():
919                     return
920                 game.resting = False
921                 game.optime = 0.0 # actually finished
922                 return
923     # Check for intelligence report
924     if not game.idebug and \
925         (rnd.withprob(0.8) or \
926          (not communicating()) or \
927          not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].charted):
928         return
929     announce()
930     prout(_("Lt. Uhura-  \"Captain, Starfleet Intelligence reports"))
931     prout(_("   the Super-commander is in Quadrant %s.") % game.state.kscmdr)
932     return
933
934 def movetholian():
935     "Move the Tholian."
936     if not game.tholian or game.justin:
937         return
938     tid = Coord()
939     if game.tholian.location.i == 0 and game.tholian.location.j == 0:
940         tid.i = 0
941         tid.j = QUADSIZE-1
942     elif game.tholian.location.i == 0 and game.tholian.location.j == QUADSIZE-1:
943         tid.i = QUADSIZE-1
944         tid.j = QUADSIZE-1
945     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == QUADSIZE-1:
946         tid.i = QUADSIZE-1
947         tid.j = 0
948     elif game.tholian.location.i == QUADSIZE-1 and game.tholian.location.j == 0:
949         tid.i = 0
950         tid.j = 0
951     else:
952         # something is wrong!
953         game.tholian.move(None)
954         prout("***Internal error: Tholian in a bad spot.")
955         return
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         rpow = 0.0
2078         for k in range(len(game.enemies)):
2079             aim = game.enemies[k].location
2080             ienm = game.quad[aim.i][aim.j]
2081             if msgflag:
2082                 proutn(_("Energy available= %.2f") % (avail-0.006))
2083                 skip(1)
2084                 msgflag = False
2085                 rpow = 0.0
2086             if damaged(DSRSENS) and \
2087                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
2088                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2089                 scanner.chew()
2090                 key = "IHEOL"
2091                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
2092                 continue
2093             if key == "IHEOL":
2094                 scanner.chew()
2095                 if itarg and k > kz:
2096                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        rnd.real(1.01, 1.06) + 1.0
2097                 kz = k
2098                 proutn("(")
2099                 if not damaged(DCOMPTR):
2100                     proutn("%d" % irec)
2101                 else:
2102                     proutn("??")
2103                 proutn(")  ")
2104                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
2105                 key = scanner.nexttok()
2106             if key == "IHALPHA" and scanner.sees("no"):
2107                 no = True
2108                 key = scanner.nexttok()
2109                 continue
2110             if key == "IHALPHA":
2111                 huh()
2112                 return
2113             if key == "IHEOL":
2114                 if k == 1: # Let me say I'm baffled by this
2115                     msgflag = True
2116                 continue
2117             if scanner.real < 0:
2118                 # abort out
2119                 scanner.chew()
2120                 return
2121             hits.append(scanner.real)
2122             rpow += scanner.real
2123             # If total requested is too much, inform and start over
2124             if rpow > avail:
2125                 prout(_("Available energy exceeded -- try again."))
2126                 scanner.chew()
2127                 return
2128             key = scanner.nexttok() # scan for next value
2129         if rpow == 0.0:
2130             # zero energy -- abort
2131             scanner.chew()
2132             return
2133         if key == "IHALPHA" and scanner.sees("no"):
2134             no = True
2135         game.energy -= rpow
2136         scanner.chew()
2137         if ifast:
2138             game.energy -= 200.0
2139             if checkshctrl(rpow):
2140                 return
2141         hittem(hits)
2142         game.ididit = True
2143      # Say shield raised or malfunction, if necessary
2144     if game.alldone:
2145         return
2146     if ifast:
2147         skip(1)
2148         if no == 0:
2149             if rnd.withprob(0.01):
2150                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2151                 prouts(_("         CLICK   CLICK   POP  . . ."))
2152                 prout(_(" No response, sir!"))
2153                 game.shldup = False
2154             else:
2155                 prout(_("Shields raised."))
2156         else:
2157             game.shldup = False
2158     overheat(rpow)
2159
2160
2161 def capture():
2162     game.ididit = False # Nothing if we fail
2163     game.optime = 0.0
2164
2165     # Make sure there is room in the brig
2166     if game.brigfree == 0:
2167         prout(_("Security reports the brig is already full."))
2168         return
2169
2170     if damaged(DRADIO):
2171         prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
2172         return
2173
2174     if damaged(DTRANSP):
2175         prout(_("Scotty- \"Transporter damaged, sir.\""))
2176         return
2177
2178     # find out if there are any at all
2179     if game.klhere < 1:
2180         prout(_("Uhura- \"Getting no response, sir.\""))
2181         return
2182
2183     # if there is more than one Klingon, find out which one
2184     #   Cruddy, just takes one at random.  Should ask the captain.
2185     #   Nah, just select the weakest one since it is most likely to
2186     #   surrender (Tom Almy mod)
2187     klingons = [e for e in game.enemies if e.type == 'K']
2188     weakest = sorted(klingons, key=lambda e: e.power)[0]
2189     game.optime = 0.05          # This action will take some time
2190     game.ididit = True #  So any others can strike back
2191
2192     # check out that Klingon
2193     # The algorithm isn't that great and could use some more
2194     # intelligent design
2195     # x = 300 + 25*skill;
2196     x = game.energy / (weakest.power * len(klingons))
2197     #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
2198     #      % (game.energy, weakest.power, len(klingons)))
2199     x *= 2.5    # would originally have been equivalent of 1.4,
2200                 # but we want command to work more often, more humanely
2201     #prout(_("Prob = %.4f" % x))
2202     #   x = 100; // For testing, of course!
2203     if x < rnd.real(100):
2204         # guess what, he surrendered!!!
2205         prout(_("Klingon captain at %s surrenders.") % weakest.location)
2206         i = rnd.real(200)
2207         if i > 0:
2208             prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
2209         if i > game.brigfree:
2210             prout(_("%d Klingons die because there is no room for them in the brig.") % (i-game.brigfree))
2211             i = game.brigfree
2212         game.brigfree -= i
2213         prout(_("%d captives taken") % i)
2214         deadkl(weakest.location, weakest.type, game.sector)
2215         if game.unwon()<=0:
2216             finish(FWON)
2217         return
2218
2219         # big surprise, he refuses to surrender
2220     prout(_("Fat chance, captain!"))
2221
2222 # Code from events.c begins here.
2223
2224 # This isn't a real event queue a la BSD Trek yet -- you can only have one
2225 # event of each type active at any given time.  Mostly these means we can
2226 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2227 # BSD Trek, from which we swiped the idea, can have up to 5.
2228
2229 def unschedule(evtype):
2230     "Remove an event from the schedule."
2231     game.future[evtype].date = FOREVER
2232     return game.future[evtype]
2233
2234 def is_scheduled(evtype):
2235     "Is an event of specified type scheduled."
2236     return game.future[evtype].date != FOREVER
2237
2238 def scheduled(evtype):
2239     "When will this event happen?"
2240     return game.future[evtype].date
2241
2242 def schedule(evtype, offset):
2243     "Schedule an event of specified type."
2244     game.future[evtype].date = game.state.date + offset
2245     return game.future[evtype]
2246
2247 def postpone(evtype, offset):
2248     "Postpone a scheduled event."
2249     game.future[evtype].date += offset
2250
2251 def cancelrest():
2252     "Rest period is interrupted by event."
2253     if game.resting:
2254         skip(1)
2255         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2256         if ja():
2257             game.resting = False
2258             game.optime = 0.0
2259             return True
2260     return False
2261
2262 def events():
2263     "Run through the event queue looking for things to do."
2264     i = 0
2265     fintim = game.state.date + game.optime
2266     yank = 0
2267     ictbeam = False
2268     istract = False
2269     w = Coord()
2270     hold = Coord()
2271     ev = Event()
2272     ev2 = Event()
2273
2274     def tractorbeam(yank):
2275         "Tractor-beaming cases merge here."
2276         announce()
2277         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2278         skip(1)
2279         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2280         # If Kirk & Co. screwing around on planet, handle
2281         atover(True) # atover(true) is Grab
2282         if game.alldone:
2283             return
2284         if game.icraft: # Caught in Galileo?
2285             finish(FSTRACTOR)
2286             return
2287         # Check to see if shuttle is aboard
2288         if game.iscraft == "offship":
2289             skip(1)
2290             if rnd.withprob(0.5):
2291                 prout(_("Galileo, left on the planet surface, is captured"))
2292                 prout(_("by aliens and made into a flying McDonald's."))
2293                 game.damage[DSHUTTL] = -10
2294                 game.iscraft = "removed"
2295             else:
2296                 prout(_("Galileo, left on the planet surface, is well hidden."))
2297         if evcode == FSPY:
2298             game.quadrant = game.state.kscmdr
2299         else:
2300             game.quadrant = game.state.kcmdr[i]
2301         game.sector = randplace(QUADSIZE)
2302         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2303                % (game.quadrant, game.sector))
2304         if game.resting:
2305             prout(_("(Remainder of rest/repair period cancelled.)"))
2306             game.resting = False
2307         if not game.shldup:
2308             if not damaged(DSHIELD) and game.shield > 0:
2309                 doshield(shraise=True) # raise shields
2310                 game.shldchg = False
2311             else:
2312                 prout(_("(Shields not currently useable.)"))
2313         newqad()
2314         # Adjust finish time to time of tractor beaming?
2315         # fintim = game.state.date+game.optime
2316         attack(torps_ok=False)
2317         if not game.state.kcmdr:
2318             unschedule(FTBEAM)
2319         else:
2320             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2321
2322     def destroybase():
2323         "Code merges here for any commander destroying a starbase."
2324         # Not perfect, but will have to do
2325         # Handle case where base is in same quadrant as starship
2326         if game.battle == game.quadrant:
2327             game.state.chart[game.battle.i][game.battle.j].starbase = False
2328             game.quad[game.base.i][game.base.j] = '.'
2329             game.base.invalidate()
2330             newcnd()
2331             skip(1)
2332             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2333         elif game.state.baseq and communicating():
2334             # Get word via subspace radio
2335             announce()
2336             skip(1)
2337             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2338             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2339             if game.isatb == 2:
2340                 prout(_("the Klingon Super-Commander"))
2341             else:
2342                 prout(_("a Klingon Commander"))
2343             game.state.chart[game.battle.i][game.battle.j].starbase = False
2344         # Remove Starbase from galaxy
2345         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2346         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2347         if game.isatb == 2:
2348             # reinstate a commander's base attack
2349             game.battle = hold
2350             game.isatb = 0
2351         else:
2352             game.battle.invalidate()
2353     if game.idebug:
2354         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2355         for i in range(1, NEVENTS):
2356             if   i == FSNOVA:  proutn("=== Supernova       ")
2357             elif i == FTBEAM:  proutn("=== T Beam          ")
2358             elif i == FSNAP:   proutn("=== Snapshot        ")
2359             elif i == FBATTAK: proutn("=== Base Attack     ")
2360             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2361             elif i == FSCMOVE: proutn("=== SC Move         ")
2362             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2363             elif i == FDSPROB: proutn("=== Probe Move      ")
2364             elif i == FDISTR:  proutn("=== Distress Call   ")
2365             elif i == FENSLV:  proutn("=== Enslavement     ")
2366             elif i == FREPRO:  proutn("=== Klingon Build   ")
2367             if is_scheduled(i):
2368                 prout("%.2f" % (scheduled(i)))
2369             else:
2370                 prout("never")
2371     radio_was_broken = damaged(DRADIO)
2372     hold.i = hold.j = 0
2373     while True:
2374         # Select earliest extraneous event, evcode==0 if no events
2375         evcode = FSPY
2376         if game.alldone:
2377             return
2378         datemin = fintim
2379         for l in range(1, NEVENTS):
2380             if game.future[l].date < datemin:
2381                 evcode = l
2382                 if game.idebug:
2383                     prout("== Event %d fires" % evcode)
2384                 datemin = game.future[l].date
2385         xtime = datemin-game.state.date
2386         if game.iscloaked:
2387             game.energy -= xtime*500.0
2388             if game.energy <= 0:
2389                 finish(FNRG)
2390                 return
2391         game.state.date = datemin
2392         # Decrement Federation resources and recompute remaining time
2393         game.state.remres -= (game.remkl()+4*len(game.state.kcmdr))*xtime
2394         game.recompute()
2395         if game.state.remtime <= 0:
2396             finish(FDEPLETE)
2397             return
2398         # Any crew left alive?
2399         if game.state.crew <= 0:
2400             finish(FCREW)
2401             return
2402         # Is life support adequate?
2403         if damaged(DLIFSUP) and game.condition != "docked":
2404             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2405                 finish(FLIFESUP)
2406                 return
2407             game.lsupres -= xtime
2408             if game.damage[DLIFSUP] <= xtime:
2409                 game.lsupres = game.inlsr
2410         # Fix devices
2411         repair = xtime
2412         if game.condition == "docked":
2413             repair /= DOCKFAC
2414         # Don't fix Deathray here
2415         for l in range(NDEVICES):
2416             if game.damage[l] > 0.0 and l != DDRAY:
2417                 if game.damage[l]-repair > 0.0:
2418                     game.damage[l] -= repair
2419                 else:
2420                     game.damage[l] = 0.0
2421         # If radio repaired, update star chart and attack reports
2422         if radio_was_broken and not damaged(DRADIO):
2423             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2424             prout(_("   surveillance reports are coming in."))
2425             skip(1)
2426             if not game.iseenit:
2427                 attackreport(False)
2428                 game.iseenit = True
2429             rechart()
2430             prout(_("   The star chart is now up to date.\""))
2431             skip(1)
2432         # Cause extraneous event EVCODE to occur
2433         game.optime -= xtime
2434         if evcode == FSNOVA: # Supernova
2435             announce()
2436             supernova(None)
2437             schedule(FSNOVA, expran(0.5*game.intime))
2438             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2439                 return
2440         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2441             if game.state.nscrem == 0 or game.iscloaked or \
2442                 ictbeam or istract or \
2443                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2444                 return
2445             if game.ientesc or \
2446                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2447                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2448                 (damaged(DSHIELD) and \
2449                  (game.energy < 2500 or damaged(DPHASER)) and \
2450                  (game.torps < 5 or damaged(DPHOTON))):
2451                 # Tractor-beam her!
2452                 istract = ictbeam = True
2453                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2454             else:
2455                 return
2456         elif evcode == FTBEAM: # Tractor beam
2457             if not game.state.kcmdr:
2458                 unschedule(FTBEAM)
2459                 continue
2460             i = rnd.integer(len(game.state.kcmdr))
2461             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2462             if istract or game.condition == "docked" or game.iscloaked or yank == 0:
2463                 # Drats! Have to reschedule
2464                 schedule(FTBEAM,
2465                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2466                 continue
2467             ictbeam = True
2468             tractorbeam(yank)
2469         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2470             game.snapsht = copy.deepcopy(game.state)
2471             game.state.snap = True
2472             schedule(FSNAP, expran(0.5 * game.intime))
2473         elif evcode == FBATTAK: # Commander attacks starbase
2474             if not game.state.kcmdr or not game.state.baseq:
2475                 # no can do
2476                 unschedule(FBATTAK)
2477                 unschedule(FCDBAS)
2478                 continue
2479             ibq = None  # Force battle location to persist past loop
2480             try:
2481                 for ibq in game.state.baseq:
2482                     for cmdr in game.state.kcmdr:
2483                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2484                             raise JumpOut
2485                 # no match found -- try later
2486                 schedule(FBATTAK, expran(0.3*game.intime))
2487                 unschedule(FCDBAS)
2488                 continue
2489             except JumpOut:
2490                 pass
2491             # commander + starbase combination found -- launch attack
2492             game.battle = ibq
2493             schedule(FCDBAS, rnd.real(1.0, 4.0))
2494             if game.isatb: # extra time if SC already attacking
2495                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2496             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2497             game.iseenit = False
2498             if not communicating():
2499                 continue # No warning :-(
2500             game.iseenit = True
2501             announce()
2502             skip(1)
2503             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2504             prout(_("   reports that it is under attack and that it can"))
2505             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2506             if cancelrest():
2507                 return
2508         elif evcode == FSCDBAS: # Supercommander destroys base
2509             unschedule(FSCDBAS)
2510             game.isatb = 2
2511             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2512                 continue # WAS RETURN!
2513             hold = game.battle
2514             game.battle = game.state.kscmdr
2515             destroybase()
2516         elif evcode == FCDBAS: # Commander succeeds in destroying base
2517             if evcode == FCDBAS:
2518                 unschedule(FCDBAS)
2519                 if not game.state.baseq() \
2520                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2521                     game.battle.invalidate()
2522                     continue
2523                 # find the lucky pair
2524                 for cmdr in game.state.kcmdr:
2525                     if cmdr == game.battle:
2526                         break
2527                 else:
2528                     # No action to take after all
2529                     continue
2530             destroybase()
2531         elif evcode == FSCMOVE: # Supercommander moves
2532             schedule(FSCMOVE, 0.2777)
2533             if not game.ientesc and not istract and game.isatb != 1 and \
2534                    (not game.iscate or not game.justin):
2535                 supercommander()
2536         elif evcode == FDSPROB: # Move deep space probe
2537             schedule(FDSPROB, 0.01)
2538             if not game.probe.nexttok():
2539                 if not game.probe.quadrant().valid_quadrant() or \
2540                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2541                     # Left galaxy or ran into supernova
2542                     if communicating():
2543                         announce()
2544                         skip(1)
2545                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2546                         if not game.probe.quadrant().valid_quadrant():
2547                             prout(_("has left the galaxy.\""))
2548                         else:
2549                             prout(_("is no longer transmitting.\""))
2550                     unschedule(FDSPROB)
2551                     continue
2552                 if communicating():
2553                     #announce()
2554                     skip(1)
2555                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2556             pquad = game.probe.quadrant()
2557             pdest = game.state.galaxy[pquad.i][pquad.j]
2558             if communicating():
2559                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2560                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2561                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2562                 pdest.charted = True
2563             game.probe.moves -= 1 # One less to travel
2564             if game.probe.arrived() and game.isarmed and pdest.stars:
2565                 supernova(game.probe)                # fire in the hole!
2566                 unschedule(FDSPROB)
2567                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2568                     return
2569         elif evcode == FDISTR: # inhabited system issues distress call
2570             unschedule(FDISTR)
2571             # try a whole bunch of times to find something suitable
2572             for i in range(100):
2573                 # need a quadrant which is not the current one,
2574                 # which has some stars which are inhabited and
2575                 # not already under attack, which is not
2576                 # supernova'ed, and which has some Klingons in it
2577                 w = randplace(GALSIZE)
2578                 q = game.state.galaxy[w.i][w.j]
2579                 if not (game.quadrant == w or q.planet is None or \
2580                       not q.planet.inhabited or \
2581                       q.supernova or q.status!="secure" or q.klingons<=0):
2582                     break
2583             else:
2584                 # can't seem to find one; ignore this call
2585                 if game.idebug:
2586                     prout("=== Couldn't find location for distress event.")
2587                 continue
2588             # got one!!  Schedule its enslavement
2589             ev = schedule(FENSLV, expran(game.intime))
2590             ev.quadrant = w
2591             q.status = "distressed"
2592             # tell the captain about it if we can
2593             if communicating():
2594                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2595                         % (q.planet, repr(w)))
2596                 prout(_("by a Klingon invasion fleet."))
2597                 if cancelrest():
2598                     return
2599         elif evcode == FENSLV:                # starsystem is enslaved
2600             ev = unschedule(FENSLV)
2601             # see if current distress call still active
2602             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2603             if q.klingons <= 0:
2604                 q.status = "secure"
2605                 continue
2606             q.status = "enslaved"
2607
2608             # play stork and schedule the first baby
2609             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2610             ev2.quadrant = ev.quadrant
2611
2612             # report the disaster if we can
2613             if communicating():
2614                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2615                         q.planet)
2616                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2617         elif evcode == FREPRO:                # Klingon reproduces
2618             # If we ever switch to a real event queue, we'll need to
2619             # explicitly retrieve and restore the x and y.
2620             ev = schedule(FREPRO, expran(1.0 * game.intime))
2621             # see if current distress call still active
2622             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2623             if q.klingons <= 0:
2624                 q.status = "secure"
2625                 continue
2626             if game.remkl() >= MAXKLGAME:
2627                 continue                # full right now
2628             # reproduce one Klingon
2629             w = ev.quadrant
2630             m = Coord()
2631             if game.klhere >= MAXKLQUAD:
2632                 try:
2633                     # this quadrant not ok, pick an adjacent one
2634                     for m.i in range(w.i - 1, w.i + 2):
2635                         for m.j in range(w.j - 1, w.j + 2):
2636                             if not m.valid_quadrant():
2637                                 continue
2638                             q = game.state.galaxy[m.i][m.j]
2639                             # check for this quad ok (not full & no snova)
2640                             if q.klingons >= MAXKLQUAD or q.supernova:
2641                                 continue
2642                             raise JumpOut
2643                     # search for eligible quadrant failed
2644                     continue
2645                 except JumpOut:
2646                     w = m
2647             # deliver the child
2648             q.klingons += 1
2649             if game.quadrant == w:
2650                 game.klhere += 1
2651                 newkling() # also adds it to game.enemies
2652             # recompute time left
2653             game.recompute()
2654             if communicating():
2655                 if game.quadrant == w:
2656                     prout(_("Spock- sensors indicate the Klingons have"))
2657                     prout(_("launched a warship from %s.") % q.planet)
2658                 else:
2659                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2660                     if q.planet is not None:
2661                         proutn(_("near %s ") % q.planet)
2662                     prout(_("in Quadrant %s.") % w)
2663
2664 def wait():
2665     "Wait on events."
2666     game.ididit = False
2667     while True:
2668         key = scanner.nexttok()
2669         if key  != "IHEOL":
2670             break
2671         proutn(_("How long? "))
2672         scanner.chew()
2673     if key != "IHREAL":
2674         huh()
2675         return
2676     origTime = delay = scanner.real
2677     if delay <= 0.0:
2678         return
2679     if delay >= game.state.remtime or len(game.enemies) != 0:
2680         proutn(_("Are you sure? "))
2681         if not ja():
2682             return
2683     # Alternate resting periods (events) with attacks
2684     game.resting = True
2685     while True:
2686         if delay <= 0:
2687             game.resting = False
2688         if not game.resting:
2689             prout(_("%d stardates left.") % int(game.state.remtime))
2690             return
2691         temp = game.optime = delay
2692         if len(game.enemies):
2693             rtime = rnd.real(1.0, 2.0)
2694             if rtime < temp:
2695                 temp = rtime
2696             game.optime = temp
2697         if game.optime < delay:
2698             attack(torps_ok=False)
2699         if game.alldone:
2700             return
2701         events()
2702         game.ididit = True
2703         if game.alldone:
2704             return
2705         delay -= temp
2706         # Repair Deathray if long rest at starbase
2707         if origTime-delay >= 9.99 and game.condition == "docked":
2708             game.damage[DDRAY] = 0.0
2709         # leave if quadrant supernovas
2710         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2711             break
2712     game.resting = False
2713     game.optime = 0.0
2714
2715 def nova(nov):
2716     "Star goes nova."
2717     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2718     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2719     if rnd.withprob(0.05):
2720         # Wow! We've supernova'ed
2721         supernova(game.quadrant)
2722         return
2723     # handle initial nova
2724     game.quad[nov.i][nov.j] = '.'
2725     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2726     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2727     game.state.starkl += 1
2728     # Set up queue to recursively trigger adjacent stars
2729     hits = [nov]
2730     kount = 0
2731     while hits:
2732         offset = Coord()
2733         start = hits.pop()
2734         for offset.i in range(-1, 1+1):
2735             for offset.j in range(-1, 1+1):
2736                 if offset.j == 0 and offset.i == 0:
2737                     continue
2738                 neighbor = start + offset
2739                 if not neighbor.valid_sector():
2740                     continue
2741                 iquad = game.quad[neighbor.i][neighbor.j]
2742                 # Empty space ends reaction
2743                 if iquad in ('.', '?', ' ', 'T', '#'):
2744                     pass
2745                 elif iquad == '*': # Affect another star
2746                     if rnd.withprob(0.05):
2747                         # This star supernovas
2748                         supernova(game.quadrant)
2749                         return
2750                     else:
2751                         hits.append(neighbor)
2752                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2753                         game.state.starkl += 1
2754                         proutn(crmena(True, '*', "sector", neighbor))
2755                         prout(_(" novas."))
2756                         game.quad[neighbor.i][neighbor.j] = '.'
2757                         kount += 1
2758                 elif iquad in ('P', '@'): # Destroy planet
2759                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2760                     if iquad == 'P':
2761                         game.state.nplankl += 1
2762                     else:
2763                         game.state.nworldkl += 1
2764                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2765                     game.iplnet.pclass = "destroyed"
2766                     game.iplnet = None
2767                     game.plnet.invalidate()
2768                     if game.landed:
2769                         finish(FPNOVA)
2770                         return
2771                     game.quad[neighbor.i][neighbor.j] = '.'
2772                 elif iquad == 'B': # Destroy base
2773                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2774                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2775                     game.base.invalidate()
2776                     game.state.basekl += 1
2777                     newcnd()
2778                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2779                     game.quad[neighbor.i][neighbor.j] = '.'
2780                 elif iquad in ('E', 'F'): # Buffet ship
2781                     prout(_("***Starship buffeted by nova."))
2782                     if game.shldup:
2783                         if game.shield >= 2000.0:
2784                             game.shield -= 2000.0
2785                         else:
2786                             diff = 2000.0 - game.shield
2787                             game.energy -= diff
2788                             game.shield = 0.0
2789                             game.shldup = False
2790                             prout(_("***Shields knocked out."))
2791                             game.damage[DSHIELD] += 0.005*game.damfac*rnd.real()*diff
2792                     else:
2793                         game.energy -= 2000.0
2794                     if game.energy <= 0:
2795                         finish(FNOVA)
2796                         return
2797                     # add in course nova contributes to kicking starship
2798                     if hits:
2799                         bump += (game.sector-hits[-1]).sgn()
2800                 elif iquad == 'K': # kill klingon
2801                     deadkl(neighbor, iquad, neighbor)
2802                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2803                     target = None
2804                     for ll in range(len(game.enemies)):
2805                         if game.enemies[ll].location == neighbor:
2806                             target = game.enemies[ll]
2807                             break
2808                     if target is not None:
2809                         target.power -= 800.0 # If firepower is lost, die
2810                         if target.power <= 0.0:
2811                             deadkl(neighbor, iquad, neighbor)
2812                             continue    # neighbor loop
2813                         # Else enemy gets flung by the blast wave
2814                         newc = neighbor + neighbor - start
2815                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2816                         if not newc.valid_sector():
2817                             # can't leave quadrant
2818                             skip(1)
2819                             continue
2820                         iquad1 = game.quad[newc.i][newc.j]
2821                         if iquad1 == ' ':
2822                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2823                             skip(1)
2824                             deadkl(neighbor, iquad, newc)
2825                             continue
2826                         if iquad1 != '.':
2827                             # can't move into something else
2828                             skip(1)
2829                             continue
2830                         proutn(_(", buffeted to Sector %s") % newc)
2831                         game.quad[neighbor.i][neighbor.j] = '.'
2832                         game.quad[newc.i][newc.j] = iquad
2833                         target.move(newc)
2834     # Starship affected by nova -- kick it away.
2835     dist = kount*0.1
2836     direc = ncourse[3*(bump.i+1)+bump.j+2]
2837     if direc == 0.0:
2838         dist = 0.0
2839     if dist == 0.0:
2840         return
2841     scourse = course(bearing=direc, distance=dist)
2842     game.optime = scourse.time(w=4)
2843     skip(1)
2844     prout(_("Force of nova displaces starship."))
2845     imove(scourse, noattack=True)
2846     game.optime = scourse.time(w=4)
2847     return
2848
2849 def supernova(w):
2850     "Star goes supernova."
2851     num = 0; npdead = 0
2852     if w is not None:
2853         nq = copy.copy(w)
2854     else:
2855         # Scheduled supernova -- select star at random.
2856         nstars = 0
2857         nq = Coord()
2858         for nq.i in range(GALSIZE):
2859             for nq.j in range(GALSIZE):
2860                 nstars += game.state.galaxy[nq.i][nq.j].stars
2861         if stars == 0:
2862             return # nothing to supernova exists
2863         num = rnd.integer(nstars) + 1
2864         for nq.i in range(GALSIZE):
2865             for nq.j in range(GALSIZE):
2866                 num -= game.state.galaxy[nq.i][nq.j].stars
2867                 if num <= 0:
2868                     break
2869             if num <=0:
2870                 break
2871         if game.idebug:
2872             proutn("=== Super nova here?")
2873             if ja():
2874                 nq = game.quadrant
2875     if not nq == game.quadrant or game.justin:
2876         # it isn't here, or we just entered (treat as enroute)
2877         if communicating():
2878             skip(1)
2879             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2880             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2881     else:
2882         ns = Coord()
2883         # we are in the quadrant!
2884         num = rnd.integer(game.state.galaxy[nq.i][nq.j].stars) + 1
2885         for ns.i in range(QUADSIZE):
2886             for ns.j in range(QUADSIZE):
2887                 if game.quad[ns.i][ns.j]=='*':
2888                     num -= 1
2889                     if num==0:
2890                         break
2891             if num==0:
2892                 break
2893         skip(1)
2894         prouts(_("***RED ALERT!  RED ALERT!"))
2895         skip(1)
2896         prout(_("***Incipient supernova detected at Sector %s") % ns)
2897         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2898             proutn(_("Emergency override attempts t"))
2899             prouts("***************")
2900             skip(1)
2901             stars()
2902             game.alldone = True
2903     # destroy any Klingons in supernovaed quadrant
2904     game.state.galaxy[nq.i][nq.j].klingons = 0
2905     if nq == game.state.kscmdr:
2906         # did in the Supercommander!
2907         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2908         game.iscate = False
2909         unschedule(FSCMOVE)
2910         unschedule(FSCDBAS)
2911     # Changing this to [w for w in game.state.kcmdr if w != nq]
2912     # causes regression-test failure
2913     survivors = list(filter(lambda w: w != nq, game.state.kcmdr))
2914     #comkills = len(game.state.kcmdr) - len(survivors)
2915     game.state.kcmdr = survivors
2916     if not game.state.kcmdr:
2917         unschedule(FTBEAM)
2918     # destroy Romulans and planets in supernovaed quadrant
2919     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2920     game.state.galaxy[nq.i][nq.j].romulans = 0
2921     game.state.nromrem -= nrmdead
2922     # Destroy planets
2923     for loop in range(game.inplan):
2924         if game.state.planets[loop].quadrant == nq:
2925             game.state.planets[loop].pclass = "destroyed"
2926             npdead += 1
2927     # Destroy any base in supernovaed quadrant
2928     game.state.baseq = [x for x in game.state.baseq if x != nq]
2929     # If starship caused supernova, tally up destruction
2930     if w is not None:
2931         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2932         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2933         game.state.nplankl += npdead
2934     # mark supernova in galaxy and in star chart
2935     if game.quadrant == nq or communicating():
2936         game.state.galaxy[nq.i][nq.j].supernova = True
2937     # If supernova destroys last Klingons give special message
2938     if game.unwon()==0 and not nq == game.quadrant:
2939         skip(2)
2940         if w is None:
2941             prout(_("Lucky you!"))
2942         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2943         finish(FWON)
2944         return
2945     # if some Klingons remain, continue or die in supernova
2946     if game.alldone:
2947         finish(FSNOVAED)
2948     return
2949
2950 # Code from finish.c ends here.
2951
2952 def selfdestruct():
2953     "Self-destruct maneuver. Finish with a BANG!"
2954     scanner.chew()
2955     if damaged(DCOMPTR):
2956         prout(_("Computer damaged; cannot execute destruct sequence."))
2957         return
2958     prouts(_("---WORKING---")); skip(1)
2959     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2960     prouts("   10"); skip(1)
2961     prouts("       9"); skip(1)
2962     prouts("          8"); skip(1)
2963     prouts("             7"); skip(1)
2964     prouts("                6"); skip(1)
2965     skip(1)
2966     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2967     skip(1)
2968     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2969     skip(1)
2970     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2971     skip(1)
2972     scanner.nexttok()
2973     if game.passwd != scanner.token:
2974         prouts(_("PASSWORD-REJECTED;"))
2975         skip(1)
2976         prouts(_("CONTINUITY-EFFECTED"))
2977         skip(2)
2978         return
2979     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2980     prouts("                   5"); skip(1)
2981     prouts("                      4"); skip(1)
2982     prouts("                         3"); skip(1)
2983     prouts("                            2"); skip(1)
2984     prouts("                              1"); skip(1)
2985     if rnd.withprob(0.15):
2986         prouts(_("GOODBYE-CRUEL-WORLD"))
2987         skip(1)
2988     kaboom()
2989
2990 def kaboom():
2991     stars()
2992     if game.ship=='E':
2993         prouts("***")
2994     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2995     skip(1)
2996     stars()
2997     skip(1)
2998     if len(game.enemies) != 0:
2999         whammo = 25.0 * game.energy
3000         for e in game.enemies[::-1]:
3001             if e.power*e.kdist <= whammo:
3002                 deadkl(e.location, game.quad[e.location.i][e.location.j], e.location)
3003     finish(FDILITHIUM)
3004
3005 def killrate():
3006     "Compute our rate of kils over time."
3007     elapsed = game.state.date - game.indate
3008     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
3009         return 0
3010     else:
3011         starting = (game.inkling + game.incom + game.inscom)
3012         remaining = game.unwon()
3013         return (starting - remaining)/elapsed
3014
3015 def badpoints():
3016     "Compute demerits."
3017     badpt = 5.0*game.state.starkl + \
3018             game.casual + \
3019             10.0*game.state.nplankl + \
3020             300*game.state.nworldkl + \
3021             45.0*game.nhelp +\
3022             100.0*game.state.basekl +\
3023             3.0*game.abandoned +\
3024             100*game.ncviol
3025     if game.ship == 'F':
3026         badpt += 100.0
3027     elif game.ship is None:
3028         badpt += 200.0
3029     return badpt
3030
3031 def finish(ifin):
3032     # end the game, with appropriate notifications
3033     igotit = False
3034     game.alldone = True
3035     skip(3)
3036     prout(_("It is stardate %.1f.") % game.state.date)
3037     skip(1)
3038     if ifin == FWON: # Game has been won
3039         if game.state.nromrem != 0:
3040             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
3041                   game.state.nromrem)
3042
3043         prout(_("You have smashed the Klingon invasion fleet and saved"))
3044         prout(_("the Federation."))
3045         if game.alive and game.brigcapacity-game.brigfree > 0:
3046             game.kcaptured += game.brigcapacity-game.brigfree
3047             prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
3048         game.gamewon = True
3049         if game.alive:
3050             badpt = badpoints()
3051             if badpt < 100.0:
3052                 badpt = 0.0        # Close enough!
3053             # killsPerDate >= RateMax
3054             if game.state.date-game.indate < 5.0 or \
3055                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
3056                 skip(1)
3057                 prout(_("In fact, you have done so well that Starfleet Command"))
3058                 if game.skill == SKILL_NOVICE:
3059                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
3060                 elif game.skill == SKILL_FAIR:
3061                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
3062                 elif game.skill == SKILL_GOOD:
3063                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
3064                 elif game.skill == SKILL_EXPERT:
3065                     prout(_("promotes you to Commodore Emeritus."))
3066                     skip(1)
3067                     prout(_("Now that you think you're really good, try playing"))
3068                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
3069                 elif game.skill == SKILL_EMERITUS:
3070                     skip(1)
3071                     proutn(_("Computer-  "))
3072                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
3073                     skip(2)
3074                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
3075                     skip(1)
3076                     prouts(_("  THIS-PROGRAM-MUST-SURVIVE"))
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?- MUST ? - SUR? ? -?  VI"))
3083                     skip(2)
3084                     prout(_("Now you can retire and write your own Star Trek game!"))
3085                     skip(1)
3086                 elif game.skill >= SKILL_EXPERT:
3087                     if game.thawed and not game.idebug:
3088                         prout(_("You cannot get a citation, so..."))
3089                     else:
3090                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3091                         scanner.chew()
3092                         if ja():
3093                             igotit = True
3094             # Only grant long life if alive (original didn't!)
3095             skip(1)
3096             prout(_("LIVE LONG AND PROSPER."))
3097         score()
3098         if igotit:
3099             plaque()
3100         return
3101     elif ifin == FDEPLETE: # Federation Resources Depleted
3102         prout(_("Your time has run out and the Federation has been"))
3103         prout(_("conquered.  Your starship is now Klingon property,"))
3104         prout(_("and you are put on trial as a war criminal.  On the"))
3105         proutn(_("basis of your record, you are "))
3106         if game.unwon()*3.0 > (game.inkling + game.incom + game.inscom):
3107             prout(_("acquitted."))
3108             skip(1)
3109             prout(_("LIVE LONG AND PROSPER."))
3110         else:
3111             prout(_("found guilty and"))
3112             prout(_("sentenced to death by slow torture."))
3113             game.alive = False
3114         score()
3115         return
3116     elif ifin == FLIFESUP:
3117         prout(_("Your life support reserves have run out, and"))
3118         prout(_("you die of thirst, starvation, and asphyxiation."))
3119         prout(_("Your starship is a derelict in space."))
3120     elif ifin == FNRG:
3121         prout(_("Your energy supply is exhausted."))
3122         skip(1)
3123         prout(_("Your starship is a derelict in space."))
3124     elif ifin == FBATTLE:
3125         prout(_("The %s has been destroyed in battle.") % crmshp())
3126         skip(1)
3127         prout(_("Dulce et decorum est pro patria mori."))
3128     elif ifin == FNEG3:
3129         prout(_("You have made three attempts to cross the negative energy"))
3130         prout(_("barrier which surrounds the galaxy."))
3131         skip(1)
3132         prout(_("Your navigation is abominable."))
3133         score()
3134     elif ifin == FNOVA:
3135         prout(_("Your starship has been destroyed by a nova."))
3136         prout(_("That was a great shot."))
3137         skip(1)
3138     elif ifin == FSNOVAED:
3139         prout(_("The %s has been fried by a supernova.") % crmshp())
3140         prout(_("...Not even cinders remain..."))
3141     elif ifin == FABANDN:
3142         prout(_("You have been captured by the Klingons. If you still"))
3143         prout(_("had a starbase to be returned to, you would have been"))
3144         prout(_("repatriated and given another chance. Since you have"))
3145         prout(_("no starbases, you will be mercilessly tortured to death."))
3146     elif ifin == FDILITHIUM:
3147         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3148     elif ifin == FMATERIALIZE:
3149         prout(_("Starbase was unable to re-materialize your starship."))
3150         prout(_("Sic transit gloria mundi"))
3151     elif ifin == FPHASER:
3152         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3153     elif ifin == FLOST:
3154         prout(_("You and your landing party have been"))
3155         prout(_("converted to energy, dissipating through space."))
3156     elif ifin == FMINING:
3157         prout(_("You are left with your landing party on"))
3158         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3159         skip(1)
3160         prout(_("They are very fond of \"Captain Kirk\" soup."))
3161         skip(1)
3162         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3163     elif ifin == FDPLANET:
3164         prout(_("You and your mining party perish."))
3165         skip(1)
3166         prout(_("That was a great shot."))
3167         skip(1)
3168     elif ifin == FSSC:
3169         prout(_("The Galileo is instantly annihilated by the supernova."))
3170         prout(_("You and your mining party are atomized."))
3171         skip(1)
3172         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3173         prout(_("joins the Romulans, wreaking terror on the Federation."))
3174     elif ifin == FPNOVA:
3175         prout(_("You and your mining party are atomized."))
3176         skip(1)
3177         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3178         prout(_("joins the Romulans, wreaking terror on the Federation."))
3179     elif ifin == FSTRACTOR:
3180         prout(_("The shuttle craft Galileo is also caught,"))
3181         prout(_("and breaks up under the strain."))
3182         skip(1)
3183         prout(_("Your debris is scattered for millions of miles."))
3184         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3185     elif ifin == FDRAY:
3186         prout(_("The mutants attack and kill Spock."))
3187         prout(_("Your ship is captured by Klingons, and"))
3188         prout(_("your crew is put on display in a Klingon zoo."))
3189     elif ifin == FTRIBBLE:
3190         prout(_("Tribbles consume all remaining water,"))
3191         prout(_("food, and oxygen on your ship."))
3192         skip(1)
3193         prout(_("You die of thirst, starvation, and asphyxiation."))
3194         prout(_("Your starship is a derelict in space."))
3195     elif ifin == FHOLE:
3196         prout(_("Your ship is drawn to the center of the black hole."))
3197         prout(_("You are crushed into extremely dense matter."))
3198     elif ifin == FCLOAK:
3199         game.ncviol += 1
3200         prout(_("You have violated the Treaty of Algeron."))
3201         prout(_("The Romulan Empire can never trust you again."))
3202     elif ifin == FCREW:
3203         prout(_("Your last crew member has died."))
3204     if ifin != FWON and ifin != FCLOAK and game.iscloaked:
3205         prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
3206         prout(_("You may have missed some warning messages."))
3207         skip(1)
3208     if game.ship == 'F':
3209         game.ship = None
3210     elif game.ship == 'E':
3211         game.ship = 'F'
3212     game.alive = False
3213     if game.unwon() != 0:
3214         goodies = game.state.remres/game.inresor
3215         baddies = (game.remkl() + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3216         if goodies/baddies >= rnd.real(1.0, 1.5):
3217             prout(_("As a result of your actions, a treaty with the Klingon"))
3218             prout(_("Empire has been signed. The terms of the treaty are"))
3219             if goodies/baddies >= rnd.real(3.0):
3220                 prout(_("favorable to the Federation."))
3221                 skip(1)
3222                 prout(_("Congratulations!"))
3223             else:
3224                 prout(_("highly unfavorable to the Federation."))
3225         else:
3226             prout(_("The Federation will be destroyed."))
3227     else:
3228         prout(_("Since you took the last Klingon with you, you are a"))
3229         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3230         prout(_("statue in your memory. Rest in peace, and try not"))
3231         prout(_("to think about pigeons."))
3232         game.gamewon = True
3233     score()
3234     scanner.chew()      # Clean up leftovers
3235
3236 def score():
3237     "Compute player's score."
3238     timused = game.state.date - game.indate
3239     if (timused == 0 or game.unwon() != 0) and timused < 5.0:
3240         timused = 5.0
3241     game.perdate = killrate()
3242     ithperd = 500*game.perdate + 0.5
3243     iwon = 0
3244     if game.gamewon:
3245         iwon = 100*game.skill
3246     if game.ship == 'E':
3247         klship = 0
3248     elif game.ship == 'F':
3249         klship = 1
3250     else:
3251         klship = 2
3252     dead_ordinaries= game.inkling - game.remkl() + len(game.state.kcmdr) + game.state.nscrem
3253     game.score = 10*(dead_ordinaries)\
3254              + 50*(game.incom - len(game.state.kcmdr)) \
3255              + ithperd + iwon \
3256              + 20*(game.inrom - game.state.nromrem) \
3257              + 200*(game.inscom - game.state.nscrem) \
3258                  - game.state.nromrem \
3259              + 3 * game.kcaptured \
3260              - badpoints()
3261     if not game.alive:
3262         game.score -= 200
3263     skip(2)
3264     prout(_("Your score --"))
3265     if game.inrom - game.state.nromrem:
3266         prout(_("%6d Romulans destroyed                 %5d") %
3267               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3268     if game.state.nromrem and game.gamewon:
3269         prout(_("%6d Romulans captured                  %5d") %
3270               (game.state.nromrem, game.state.nromrem))
3271     if dead_ordinaries:
3272         prout(_("%6d ordinary Klingons destroyed        %5d") %
3273               (dead_ordinaries, 10*dead_ordinaries))
3274     if game.incom - len(game.state.kcmdr):
3275         prout(_("%6d Klingon commanders destroyed       %5d") %
3276               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3277     if game.kcaptured:
3278         prout(_("%d Klingons captured                   %5d") %
3279               (game.kcaptured, 3 * game.kcaptured))
3280     if game.inscom - game.state.nscrem:
3281         prout(_("%6d Super-Commander destroyed          %5d") %
3282               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3283     if ithperd:
3284         prout(_("%6.2f Klingons per stardate              %5d") %
3285               (game.perdate, ithperd))
3286     if game.state.starkl:
3287         prout(_("%6d stars destroyed by your action     %5d") %
3288               (game.state.starkl, -5*game.state.starkl))
3289     if game.state.nplankl:
3290         prout(_("%6d planets destroyed by your action   %5d") %
3291               (game.state.nplankl, -10*game.state.nplankl))
3292     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3293         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3294               (game.state.nworldkl, -300*game.state.nworldkl))
3295     if game.state.basekl:
3296         prout(_("%6d bases destroyed by your action     %5d") %
3297               (game.state.basekl, -100*game.state.basekl))
3298     if game.nhelp:
3299         prout(_("%6d calls for help from starbase       %5d") %
3300               (game.nhelp, -45*game.nhelp))
3301     if game.casual:
3302         prout(_("%6d casualties incurred                %5d") %
3303               (game.casual, -game.casual))
3304     if game.abandoned:
3305         prout(_("%6d crew abandoned in space            %5d") %
3306               (game.abandoned, -3*game.abandoned))
3307     if klship:
3308         prout(_("%6d ship(s) lost or destroyed          %5d") %
3309               (klship, -100*klship))
3310     if game.ncviol > 0:
3311         if game.ncviol == 1:
3312             prout(_("1 Treaty of Algeron violation          -100"))
3313         else:
3314             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3315                   (game.ncviol, -100*game.ncviol))
3316     if not game.alive:
3317         prout(_("Penalty for getting yourself killed        -200"))
3318     if game.gamewon:
3319         proutn(_("Bonus for winning "))
3320         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3321         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3322         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3323         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3324         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3325         prout("           %5d" % iwon)
3326     skip(1)
3327     prout(_("TOTAL SCORE                               %5d") % game.score)
3328
3329 def plaque():
3330     "Emit winner's commemmorative plaque."
3331     skip(2)
3332     while True:
3333         proutn(_("File or device name for your plaque: "))
3334         winner = cgetline()
3335         try:
3336             fp = open(winner, "w")
3337             break
3338         except IOError:
3339             prout(_("Invalid name."))
3340
3341     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3342     winner = cgetline()
3343     # The 38 below must be 64 for 132-column paper
3344     nskip = 38 - len(winner)/2
3345     # This is where the ASCII art picture was emitted.
3346     # It got garbled somewhere in the chain of transmission to the Almy version.
3347     # We should restore it if we can find old enough FORTRAN sources.
3348     fp.write("\n\n\n")
3349     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3350     fp.write("\n\n\n\n")
3351     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3352     fp.write("\n")
3353     fp.write(_("                                                Starfleet Command bestows to you\n"))
3354     fp.write("\n")
3355     fp.write("%*s%s\n\n" % (nskip, "", winner))
3356     fp.write(_("                                                           the rank of\n\n"))
3357     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3358     fp.write("                                                          ")
3359     if game.skill ==  SKILL_EXPERT:
3360         fp.write(_(" Expert level\n\n"))
3361     elif game.skill == SKILL_EMERITUS:
3362         fp.write(_("Emeritus level\n\n"))
3363     else:
3364         fp.write(_(" Cheat level\n\n"))
3365     timestring = time.ctime()
3366     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3367              (timestring+4, timestring+20, timestring+11))
3368     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3369     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3370     fp.close()
3371
3372 # Code from io.c begins here
3373
3374 rows = linecount = 0        # for paging
3375 stdscr = None
3376 replayfp = None
3377 fullscreen_window = None
3378 srscan_window     = None   # Short range scan
3379 report_window     = None   # Report legends for status window
3380 status_window     = None   # The status window itself
3381 lrscan_window     = None   # Long range scan
3382 message_window    = None   # Main window for scrolling text
3383 prompt_window     = None   # Prompt window at bottom of display
3384 curwnd = None
3385
3386 def iostart():
3387     global stdscr, rows
3388     # for some recent versions of python2, the following enables UTF8
3389     # for the older ones we probably need to set C locale, and python3
3390     # has no problems at all
3391     if sys.version_info[0] < 3:
3392         locale.setlocale(locale.LC_ALL, "")
3393     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3394     gettext.textdomain("sst")
3395     if not (game.options & OPTION_CURSES):
3396         ln_env = os.getenv("LINES")
3397         if ln_env:
3398             rows = ln_env
3399         else:
3400             rows = 25
3401     else:
3402         stdscr = curses.initscr()
3403         stdscr.keypad(True)
3404         curses.nonl()
3405         curses.cbreak()
3406         if game.options & OPTION_COLOR:
3407             curses.start_color()
3408             curses.use_default_colors()
3409             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3410             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3411             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3412             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3413             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3414             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3415             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3416             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3417         global fullscreen_window, srscan_window, report_window, status_window
3418         global lrscan_window, message_window, prompt_window
3419         (rows, _columns)   = stdscr.getmaxyx()
3420         fullscreen_window = stdscr
3421         srscan_window     = curses.newwin(12, 25, 0,       0)
3422         report_window     = curses.newwin(11, 0,  1,       25)
3423         status_window     = curses.newwin(10, 0,  1,       39)
3424         lrscan_window     = curses.newwin(5,  0,  0,       64)
3425         message_window    = curses.newwin(0,  0,  12,      0)
3426         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3427         message_window.scrollok(True)
3428         setwnd(fullscreen_window)
3429
3430 def ioend():
3431     "Wrap up I/O."
3432     if game.options & OPTION_CURSES:
3433         stdscr.keypad(False)
3434         curses.echo()
3435         curses.nocbreak()
3436         curses.endwin()
3437
3438 def waitfor():
3439     "Wait for user action -- OK to do nothing if on a TTY"
3440     if game.options & OPTION_CURSES:
3441         stdscr.getch()
3442
3443 def announce():
3444     skip(1)
3445     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3446     skip(1)
3447
3448 def pause_game():
3449     if game.skill > SKILL_FAIR:
3450         prompt = _("[CONTINUE?]")
3451     else:
3452         prompt = _("[PRESS ENTER TO CONTINUE]")
3453
3454     if game.options & OPTION_CURSES:
3455         drawmaps(0)
3456         setwnd(prompt_window)
3457         prompt_window.clear()
3458         prompt_window.addstr(prompt)
3459         prompt_window.getstr()
3460         prompt_window.clear()
3461         prompt_window.refresh()
3462         setwnd(message_window)
3463     else:
3464         global linecount
3465         sys.stdout.write('\n')
3466         proutn(prompt)
3467         if not replayfp:
3468             my_input()
3469         sys.stdout.write('\n' * rows)
3470         linecount = 0
3471
3472 def skip(i):
3473     "Skip i lines.  Pause game if this would cause a scrolling event."
3474     for _dummy in range(i):
3475         if game.options & OPTION_CURSES:
3476             (y, _x) = curwnd.getyx()
3477             try:
3478                 curwnd.move(y+1, 0)
3479             except curses.error:
3480                 pass
3481         else:
3482             global linecount
3483             linecount += 1
3484             if rows and linecount >= rows:
3485                 pause_game()
3486             else:
3487                 sys.stdout.write('\n')
3488
3489 def proutn(proutntline):
3490     "Utter a line with no following line feed."
3491     if game.options & OPTION_CURSES:
3492         (y, x) = curwnd.getyx()
3493         (my, _mx) = curwnd.getmaxyx()
3494         if curwnd == message_window and y >= my - 2:
3495             pause_game()
3496             clrscr()
3497         if logfp and game.cdebug:
3498             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3499         curwnd.addstr(proutntline)
3500         curwnd.refresh()
3501     else:
3502         sys.stdout.write(proutntline)
3503         sys.stdout.flush()
3504
3505 def prout(proutline):
3506     proutn(proutline)
3507     skip(1)
3508
3509 def prouts(proutsline):
3510     "Emit slowly!"
3511     for c in proutsline:
3512         if not replayfp or replayfp.closed:        # Don't slow down replays
3513             time.sleep(0.03)
3514         proutn(c)
3515         if game.options & OPTION_CURSES:
3516             curwnd.refresh()
3517         else:
3518             sys.stdout.flush()
3519     if not replayfp or replayfp.closed:
3520         time.sleep(0.03)
3521
3522 def cgetline():
3523     "Get a line of input."
3524     if game.options & OPTION_CURSES:
3525         linein = curwnd.getstr() + "\n"
3526         curwnd.refresh()
3527     else:
3528         if replayfp and not replayfp.closed:
3529             while True:
3530                 linein = replayfp.readline()
3531                 proutn(linein)
3532                 if linein == '':
3533                     prout("*** Replay finished")
3534                     replayfp.close()
3535                     break
3536                 elif linein[0] != "#":
3537                     break
3538         else:
3539             try:
3540                 linein = my_input() + "\n"
3541             except EOFError:
3542                 prout("")
3543                 sys.exit(0)
3544     if logfp:
3545         logfp.write(linein)
3546     return linein
3547
3548 def setwnd(wnd):
3549     "Change windows -- OK for this to be a no-op in tty mode."
3550     global curwnd
3551     if game.options & OPTION_CURSES:
3552         if game.cdebug and logfp:
3553             if wnd == fullscreen_window:
3554                 legend = "fullscreen"
3555             elif wnd == srscan_window:
3556                 legend = "srscan"
3557             elif wnd == report_window:
3558                 legend = "report"
3559             elif wnd == status_window:
3560                 legend = "status"
3561             elif wnd == lrscan_window:
3562                 legend = "lrscan"
3563             elif wnd == message_window:
3564                 legend = "message"
3565             elif wnd == prompt_window:
3566                 legend = "prompt"
3567             else:
3568                 legend = "unknown"
3569             logfp.write("#curses: setwnd(%s)\n" % legend)
3570         curwnd = wnd
3571         # Some curses implementations get confused when you try this.
3572         try:
3573             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3574         except curses.error:
3575             pass
3576
3577 def clreol():
3578     "Clear to end of line -- can be a no-op in tty mode"
3579     if game.options & OPTION_CURSES:
3580         curwnd.clrtoeol()
3581         curwnd.refresh()
3582
3583 def clrscr():
3584     "Clear screen -- can be a no-op in tty mode."
3585     global linecount
3586     if game.options & OPTION_CURSES:
3587         curwnd.clear()
3588         curwnd.move(0, 0)
3589         curwnd.refresh()
3590     linecount = 0
3591
3592 def textcolor(color=DEFAULT):
3593     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3594         if color == DEFAULT:
3595             curwnd.attrset(0)
3596         elif color ==  BLACK:
3597             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3598         elif color ==  BLUE:
3599             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3600         elif color ==  GREEN:
3601             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3602         elif color ==  CYAN:
3603             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3604         elif color ==  RED:
3605             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3606         elif color ==  MAGENTA:
3607             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3608         elif color ==  BROWN:
3609             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3610         elif color ==  LIGHTGRAY:
3611             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3612         elif color ==  DARKGRAY:
3613             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3614         elif color ==  LIGHTBLUE:
3615             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3616         elif color ==  LIGHTGREEN:
3617             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3618         elif color ==  LIGHTCYAN:
3619             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3620         elif color ==  LIGHTRED:
3621             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3622         elif color ==  LIGHTMAGENTA:
3623             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3624         elif color ==  YELLOW:
3625             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3626         elif color ==  WHITE:
3627             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3628
3629 def highvideo():
3630     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3631         curwnd.attron(curses.A_REVERSE)
3632
3633 #
3634 # Things past this point have policy implications.
3635 #
3636
3637 def drawmaps(mode):
3638     "Hook to be called after moving to redraw maps."
3639     if game.options & OPTION_CURSES:
3640         if mode == 1:
3641             sensor()
3642         setwnd(srscan_window)
3643         curwnd.move(0, 0)
3644         srscan()
3645         if mode != 2:
3646             setwnd(status_window)
3647             status_window.clear()
3648             status_window.move(0, 0)
3649             setwnd(report_window)
3650             report_window.clear()
3651             report_window.move(0, 0)
3652             status()
3653             setwnd(lrscan_window)
3654             lrscan_window.clear()
3655             lrscan_window.move(0, 0)
3656             lrscan(silent=False)
3657
3658 def put_srscan_sym(w, sym):
3659     "Emit symbol for short-range scan."
3660     srscan_window.move(w.i+1, w.j*2+2)
3661     srscan_window.addch(sym)
3662     srscan_window.refresh()
3663
3664 def boom(w):
3665     "Enemy fall down, go boom."
3666     if game.options & OPTION_CURSES:
3667         drawmaps(0)
3668         setwnd(srscan_window)
3669         srscan_window.attron(curses.A_REVERSE)
3670         put_srscan_sym(w, game.quad[w.i][w.j])
3671         #sound(500)
3672         #time.sleep(1.0)
3673         #nosound()
3674         srscan_window.attroff(curses.A_REVERSE)
3675         put_srscan_sym(w, game.quad[w.i][w.j])
3676         curses.delay_output(500)
3677         setwnd(message_window)
3678
3679 def warble():
3680     "Sound and visual effects for teleportation."
3681     if game.options & OPTION_CURSES:
3682         drawmaps(2)
3683         setwnd(message_window)
3684         #sound(50)
3685     prouts("     . . . . .     ")
3686     if game.options & OPTION_CURSES:
3687         #curses.delay_output(1000)
3688         #nosound()
3689         pass
3690
3691 def tracktorpedo(w, step, i, n, iquad):
3692     "Torpedo-track animation."
3693     if not game.options & OPTION_CURSES:
3694         if step == 1:
3695             if n != 1:
3696                 skip(1)
3697                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3698             else:
3699                 skip(1)
3700                 proutn(_("Torpedo track- "))
3701         elif step==4 or step==9:
3702             skip(1)
3703         proutn("%s   " % w)
3704     else:
3705         if not damaged(DSRSENS) or game.condition=="docked":
3706             if i != 0 and step == 1:
3707                 drawmaps(2)
3708                 time.sleep(0.4)
3709             if (iquad=='.') or (iquad==' '):
3710                 put_srscan_sym(w, '+')
3711                 #sound(step*10)
3712                 #time.sleep(0.1)
3713                 #nosound()
3714                 put_srscan_sym(w, iquad)
3715             else:
3716                 curwnd.attron(curses.A_REVERSE)
3717                 put_srscan_sym(w, iquad)
3718                 #sound(500)
3719                 #time.sleep(1.0)
3720                 #nosound()
3721                 curwnd.attroff(curses.A_REVERSE)
3722                 put_srscan_sym(w, iquad)
3723         else:
3724             proutn("%s   " % w)
3725
3726 def makechart():
3727     "Display the current galaxy chart."
3728     if game.options & OPTION_CURSES:
3729         setwnd(message_window)
3730         message_window.clear()
3731     chart()
3732     if game.options & OPTION_TTY:
3733         skip(1)
3734
3735 NSYM        = 14
3736
3737 def prstat(txt, data):
3738     proutn(txt)
3739     if game.options & OPTION_CURSES:
3740         skip(1)
3741         setwnd(status_window)
3742     else:
3743         proutn(" " * (NSYM - len(txt)))
3744     proutn(data)
3745     skip(1)
3746     if game.options & OPTION_CURSES:
3747         setwnd(report_window)
3748
3749 # Code from moving.c begins here
3750
3751 def imove(icourse=None, noattack=False):
3752     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3753     w = Coord()
3754
3755     def newquadrant(noattack):
3756         # Leaving quadrant -- allow final enemy attack
3757         # Don't set up attack if being pushed by nova or cloaked
3758         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3759             newcnd()
3760             for enemy in game.enemies:
3761                 finald = (w - enemy.location).distance()
3762                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3763             # Stas Sergeev added the condition
3764             # that attacks only happen if Klingons
3765             # are present and your skill is good.
3766             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3767                 attack(torps_ok=False)
3768             if game.alldone:
3769                 return
3770         # check for edge of galaxy
3771         kinks = 0
3772         while True:
3773             kink = False
3774             if icourse.final.i < 0:
3775                 icourse.final.i = -icourse.final.i
3776                 kink = True
3777             if icourse.final.j < 0:
3778                 icourse.final.j = -icourse.final.j
3779                 kink = True
3780             if icourse.final.i >= GALSIZE*QUADSIZE:
3781                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3782                 kink = True
3783             if icourse.final.j >= GALSIZE*QUADSIZE:
3784                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3785                 kink = True
3786             if kink:
3787                 kinks += 1
3788             else:
3789                 break
3790         if kinks:
3791             game.nkinks += 1
3792             if game.nkinks == 3:
3793                 # Three strikes -- you're out!
3794                 finish(FNEG3)
3795                 return
3796             skip(1)
3797             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3798             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3799             prout(_("YOU WILL BE DESTROYED."))
3800         # Compute final position in new quadrant
3801         if trbeam: # Don't bother if we are to be beamed
3802             return
3803         game.quadrant = icourse.final.quadrant()
3804         game.sector = icourse.final.sector()
3805         skip(1)
3806         prout(_("Entering Quadrant %s.") % game.quadrant)
3807         game.quad[game.sector.i][game.sector.j] = game.ship
3808         newqad()
3809         if game.skill>SKILL_NOVICE:
3810             attack(torps_ok=False)
3811
3812     def check_collision(h):
3813         iquad = game.quad[h.i][h.j]
3814         if iquad != '.':
3815             # object encountered in flight path
3816             stopegy = 50.0*icourse.distance/game.optime
3817             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3818                 for enemy in game.enemies:
3819                     if enemy.location == game.sector:
3820                         collision(rammed=False, enemy=enemy)
3821                         return True
3822                 # This should not happen
3823                 prout(_("Which way did he go?"))
3824                 return False
3825             elif iquad == ' ':
3826                 skip(1)
3827                 prouts(_("***RED ALERT!  RED ALERT!"))
3828                 skip(1)
3829                 proutn("***" + crmshp())
3830                 proutn(_(" pulled into black hole at Sector %s") % h)
3831                 # Getting pulled into a black hole was certain
3832                 # death in Almy's original.  Stas Sergeev added a
3833                 # possibility that you'll get timewarped instead.
3834                 n=0
3835                 for m in range(NDEVICES):
3836                     if game.damage[m]>0:
3837                         n += 1
3838                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3839                 if (game.options & OPTION_BLKHOLE) and rnd.withprob(1-probf):
3840                     timwrp()
3841                 else:
3842                     finish(FHOLE)
3843                 return True
3844             else:
3845                 # something else
3846                 skip(1)
3847                 proutn(crmshp())
3848                 if iquad == '#':
3849                     prout(_(" encounters Tholian web at %s;") % h)
3850                 else:
3851                     prout(_(" blocked by object at %s;") % h)
3852                 proutn(_("Emergency stop required "))
3853                 prout(_("%2d units of energy.") % int(stopegy))
3854                 game.energy -= stopegy
3855                 if game.energy <= 0:
3856                     finish(FNRG)
3857                 return True
3858         return False
3859
3860     trbeam = False
3861     if game.inorbit:
3862         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3863         game.inorbit = False
3864     # If tractor beam is to occur, don't move full distance
3865     if game.state.date+game.optime >= scheduled(FTBEAM):
3866         if game.iscloaked:
3867             # We can't be tractor beamed if cloaked,
3868             # so move the event into the future
3869             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
3870         else:
3871             trbeam = True
3872             game.condition = "red"
3873             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3874             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3875     # Move out
3876     game.quad[game.sector.i][game.sector.j] = '.'
3877     for _m in range(icourse.moves):
3878         icourse.nexttok()
3879         w = icourse.sector()
3880         if icourse.origin.quadrant() != icourse.location.quadrant():
3881             newquadrant(noattack)
3882             break
3883         elif check_collision(w):
3884             prout(_("Collision detected"))
3885             break
3886         else:
3887             game.sector = w
3888     # We're in destination quadrant -- compute new average enemy distances
3889     game.quad[game.sector.i][game.sector.j] = game.ship
3890     if game.enemies:
3891         for enemy in game.enemies:
3892             finald = (w-enemy.location).distance()
3893             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3894             enemy.kdist = finald
3895         sortenemies()
3896         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3897             attack(torps_ok=False)
3898         for enemy in game.enemies:
3899             enemy.kavgd = enemy.kdist
3900     newcnd()
3901     drawmaps(0)
3902     setwnd(message_window)
3903     return
3904
3905 def dock(verbose):
3906     "Dock our ship at a starbase."
3907     scanner.chew()
3908     if game.condition == "docked" and verbose:
3909         prout(_("Already docked."))
3910         return
3911     if game.inorbit:
3912         prout(_("You must first leave standard orbit."))
3913         return
3914     if game.base is None or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3915         prout(crmshp() + _(" not adjacent to base."))
3916         return
3917     if game.iscloaked:
3918         prout(_("You cannot dock while cloaked."))
3919         return
3920     game.condition = "docked"
3921     if verbose:
3922         prout(_("Docked."))
3923     game.ididit = True
3924     if game.energy < game.inenrg:
3925         game.energy = game.inenrg
3926     game.shield = game.inshld
3927     game.torps = game.intorps
3928     game.lsupres = game.inlsr
3929     game.state.crew = FULLCREW
3930     if game.brigcapacity-game.brigfree > 0:
3931         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3932         game.kcaptured += game.brigcapacity-game.brigfree
3933         game.brigfree = game.brigcapacity
3934     if communicating() and \
3935         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3936         # get attack report from base
3937         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3938         attackreport(False)
3939         game.iseenit = True
3940
3941 def cartesian(loc1=None, loc2=None):
3942     if loc1 is None:
3943         return game.quadrant * QUADSIZE + game.sector
3944     elif loc2 is None:
3945         return game.quadrant * QUADSIZE + loc1
3946     else:
3947         return loc1 * QUADSIZE + loc2
3948
3949 def getcourse(isprobe):
3950     "Get a course and distance from the user."
3951     key = ""
3952     dquad = copy.copy(game.quadrant)
3953     navmode = "unspecified"
3954     itemp = "curt"
3955     dsect = Coord()
3956     iprompt = False
3957     if game.landed and not isprobe:
3958         prout(_("Dummy! You can't leave standard orbit until you"))
3959         proutn(_("are back aboard the ship."))
3960         scanner.chew()
3961         raise TrekError
3962     while navmode == "unspecified":
3963         if damaged(DNAVSYS):
3964             if isprobe:
3965                 prout(_("Computer damaged; manual navigation only"))
3966             else:
3967                 prout(_("Computer damaged; manual movement only"))
3968             scanner.chew()
3969             navmode = "manual"
3970             key = "IHEOL"
3971             break
3972         key = scanner.nexttok()
3973         if key == "IHEOL":
3974             proutn(_("Manual or automatic- "))
3975             iprompt = True
3976             scanner.chew()
3977         elif key == "IHALPHA":
3978             if scanner.sees("manual"):
3979                 navmode = "manual"
3980                 key = scanner.nexttok()
3981                 break
3982             elif scanner.sees("automatic"):
3983                 navmode = "automatic"
3984                 key = scanner.nexttok()
3985                 break
3986             else:
3987                 huh()
3988                 scanner.chew()
3989                 raise TrekError
3990         else: # numeric
3991             if isprobe:
3992                 prout(_("(Manual navigation assumed.)"))
3993             else:
3994                 prout(_("(Manual movement assumed.)"))
3995             navmode = "manual"
3996             break
3997     delta = Coord()
3998     if navmode == "automatic":
3999         while key == "IHEOL":
4000             if isprobe:
4001                 proutn(_("Target quadrant or quadrant&sector- "))
4002             else:
4003                 proutn(_("Destination sector or quadrant&sector- "))
4004             scanner.chew()
4005             iprompt = True
4006             key = scanner.nexttok()
4007         if key != "IHREAL":
4008             huh()
4009             raise TrekError
4010         xi = int(round(scanner.real))-1
4011         key = scanner.nexttok()
4012         if key != "IHREAL":
4013             huh()
4014             raise TrekError
4015         xj = int(round(scanner.real))-1
4016         key = scanner.nexttok()
4017         if key == "IHREAL":
4018             # both quadrant and sector specified
4019             xk = int(round(scanner.real))-1
4020             key = scanner.nexttok()
4021             if key != "IHREAL":
4022                 huh()
4023                 raise TrekError
4024             xl = int(round(scanner.real))-1
4025             dquad.i = xi
4026             dquad.j = xj
4027             dsect.i = xk
4028             dsect.j = xl
4029         else:
4030             # only one pair of numbers was specified
4031             if isprobe:
4032                 # only quadrant specified -- go to center of dest quad
4033                 dquad.i = xi
4034                 dquad.j = xj
4035                 dsect.j = dsect.i = 4        # preserves 1-origin behavior
4036             else:
4037                 # only sector specified
4038                 dsect.i = xi
4039                 dsect.j = xj
4040             itemp = "normal"
4041         if not dquad.valid_quadrant() or not dsect.valid_sector():
4042             huh()
4043             raise TrekError
4044         skip(1)
4045         if not isprobe:
4046             if itemp > "curt":
4047                 if iprompt:
4048                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
4049             else:
4050                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
4051         # the actual deltas get computed here
4052         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
4053         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
4054     else: # manual
4055         while key == "IHEOL":
4056             proutn(_("X and Y displacements- "))
4057             scanner.chew()
4058             iprompt = True
4059             key = scanner.nexttok()
4060         itemp = "verbose"
4061         if key == "IHREAL":
4062             delta.j = scanner.real
4063         else:
4064             huh()
4065             raise TrekError
4066         key = scanner.nexttok()
4067         if key == "IHREAL":
4068             delta.i = scanner.real
4069         elif key == "IHEOL":
4070             delta.i = 0
4071             scanner.push("\n")
4072         else:
4073             huh()
4074             raise TrekError
4075     # Check for zero movement
4076     if delta.i == 0 and delta.j == 0:
4077         scanner.chew()
4078         raise TrekError
4079     if itemp == "verbose" and not isprobe:
4080         skip(1)
4081         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4082     scanner.chew()
4083     return course(bearing=delta.bearing(), distance=delta.distance())
4084
4085 class course:
4086     def __init__(self, bearing, distance, origin=None):
4087         self.distance = distance
4088         self.bearing = bearing
4089         if origin is None:
4090             self.origin = cartesian(game.quadrant, game.sector)
4091         else:
4092             self.origin = origin
4093         # The bearing() code we inherited from FORTRAN is actually computing
4094         # clockface directions!
4095         if self.bearing < 0.0:
4096             self.bearing += 12.0
4097         self.angle = ((15.0 - self.bearing) * 0.5235988)
4098         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4099         bigger = max(abs(self.increment.i), abs(self.increment.j))
4100         self.increment /= bigger
4101         self.moves = int(round(10*self.distance*bigger))
4102         self.reset()
4103         self.final = (self.location + self.moves*self.increment).roundtogrid()
4104         self.location = self.origin
4105         self.nextlocation = None
4106     def reset(self):
4107         self.location = self.origin
4108         self.step = 0
4109     def arrived(self):
4110         return self.location.roundtogrid() == self.final
4111     def nexttok(self):
4112         "Next step on course."
4113         self.step += 1
4114         self.nextlocation = self.location + self.increment
4115         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4116         self.location = self.nextlocation
4117         return samequad
4118     def quadrant(self):
4119         return self.location.quadrant()
4120     def sector(self):
4121         return self.location.sector()
4122     def power(self, w):
4123         return self.distance*(w**3)*(game.shldup+1)
4124     def time(self, w):
4125         return 10.0*self.distance/w**2
4126
4127 def impulse():
4128     "Move under impulse power."
4129     game.ididit = False
4130     if damaged(DIMPULS):
4131         scanner.chew()
4132         skip(1)
4133         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4134         return
4135     if game.energy > 30.0:
4136         try:
4137             icourse = getcourse(isprobe=False)
4138         except TrekError:
4139             return
4140         power = 20.0 + 100.0*icourse.distance
4141     else:
4142         power = 30.0
4143     if power >= game.energy:
4144         # Insufficient power for trip
4145         skip(1)
4146         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4147         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4148         if game.energy > 30:
4149             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4150                    int(0.01 * (game.energy-20.0)-0.05))
4151             prout(_(" quadrants.\""))
4152         else:
4153             prout(_("quadrant.  They are, therefore, useless.\""))
4154         scanner.chew()
4155         return
4156     # Make sure enough time is left for the trip
4157     game.optime = icourse.distance/0.095
4158     if game.optime >= game.state.remtime:
4159         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4160         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4161         proutn(_("we dare spend the time?\" "))
4162         if not ja():
4163             return
4164     # Activate impulse engines and pay the cost
4165     imove(icourse, noattack=False)
4166     game.ididit = True
4167     if game.alldone:
4168         return
4169     power = 20.0 + 100.0*icourse.distance
4170     game.energy -= power
4171     game.optime = icourse.distance/0.095
4172     if game.energy <= 0:
4173         finish(FNRG)
4174     return
4175
4176 def warp(wcourse, involuntary):
4177     "ove under warp drive."
4178     blooey = False; twarp = False
4179     if not involuntary: # Not WARPX entry
4180         game.ididit = False
4181         if game.iscloaked:
4182             scanner.chew()
4183             skip(1)
4184             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4185             return
4186         if game.damage[DWARPEN] > 10.0:
4187             scanner.chew()
4188             skip(1)
4189             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4190             return
4191         if damaged(DWARPEN) and game.warpfac > 4.0:
4192             scanner.chew()
4193             skip(1)
4194             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4195             prout(_("  is repaired, I can only give you warp 4.\""))
4196             return
4197                # Read in course and distance
4198         if wcourse is None:
4199             try:
4200                 wcourse = getcourse(isprobe=False)
4201             except TrekError:
4202                 return
4203         # Make sure starship has enough energy for the trip
4204         # Note: this formula is slightly different from the C version,
4205         # and lets you skate a bit closer to the edge.
4206         if wcourse.power(game.warpfac) >= game.energy:
4207             # Insufficient power for trip
4208             game.ididit = False
4209             skip(1)
4210             prout(_("Engineering to bridge--"))
4211             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4212                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4213                 if iwarp <= 0:
4214                     prout(_("We can't do it, Captain. We don't have enough energy."))
4215                 else:
4216                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4217                     if game.shldup:
4218                         prout(",")
4219                         prout(_("if you'll lower the shields."))
4220                     else:
4221                         prout(".")
4222             else:
4223                 prout(_("We haven't the energy to go that far with the shields up."))
4224             return
4225         # Make sure enough time is left for the trip
4226         game.optime = wcourse.time(game.warpfac)
4227         if game.optime >= 0.8*game.state.remtime:
4228             skip(1)
4229             prout(_("First Officer Spock- \"Captain, I compute that such"))
4230             proutn(_("  a trip would require approximately %2.0f") %
4231                    (100.0*game.optime/game.state.remtime))
4232             prout(_(" percent of our"))
4233             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4234             if not ja():
4235                 game.ididit = False
4236                 game.optime=0
4237                 return
4238     # Entry WARPX
4239     if game.warpfac > 6.0:
4240         # Decide if engine damage will occur
4241         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4242         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4243         if prob > rnd.real():
4244             blooey = True
4245             wcourse.distance = rnd.real(wcourse.distance)
4246         # Decide if time warp will occur
4247         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > rnd.real():
4248             twarp = True
4249         if game.idebug and game.warpfac==10 and not twarp:
4250             blooey = False
4251             proutn("=== Force time warp? ")
4252             if ja():
4253                 twarp = True
4254         if blooey or twarp:
4255             # If time warp or engine damage, check path
4256             # If it is obstructed, don't do warp or damage
4257             look = wcourse.moves
4258             while look > 0:
4259                 look -= 1
4260                 wcourse.nexttok()
4261                 w = wcourse.sector()
4262                 if not w.valid_sector():
4263                     break
4264                 if game.quad[w.i][w.j] != '.':
4265                     blooey = False
4266                     twarp = False
4267             wcourse.reset()
4268     # Activate Warp Engines and pay the cost
4269     imove(wcourse, noattack=False)
4270     if game.alldone:
4271         return
4272     game.energy -= wcourse.power(game.warpfac)
4273     if game.energy <= 0:
4274         finish(FNRG)
4275     game.optime = wcourse.time(game.warpfac)
4276     if twarp:
4277         timwrp()
4278     if blooey:
4279         game.damage[DWARPEN] = game.damfac * rnd.real(1.0, 4.0)
4280         skip(1)
4281         prout(_("Engineering to bridge--"))
4282         prout(_("  Scott here.  The warp engines are damaged."))
4283         prout(_("  We'll have to reduce speed to warp 4."))
4284     game.ididit = True
4285     return
4286
4287 def setwarp():
4288     "Change the warp factor."
4289     while True:
4290         key=scanner.nexttok()
4291         if key != "IHEOL":
4292             break
4293         scanner.chew()
4294         proutn(_("Warp factor- "))
4295     if key != "IHREAL":
4296         huh()
4297         return
4298     if game.damage[DWARPEN] > 10.0:
4299         prout(_("Warp engines inoperative."))
4300         return
4301     if damaged(DWARPEN) and scanner.real > 4.0:
4302         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4303         prout(_("  but right now we can only go warp 4.\""))
4304         return
4305     if scanner.real > 10.0:
4306         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4307         return
4308     if scanner.real < 1.0:
4309         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4310         return
4311     oldfac = game.warpfac
4312     game.warpfac = scanner.real
4313     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4314         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4315               int(game.warpfac))
4316         return
4317     if game.warpfac < 8.00:
4318         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4319         return
4320     if game.warpfac == 10.0:
4321         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4322         return
4323     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4324     return
4325
4326 def atover(igrab):
4327     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4328     scanner.chew()
4329     # is captain on planet?
4330     if game.landed:
4331         if damaged(DTRANSP):
4332             finish(FPNOVA)
4333             return
4334         prout(_("Scotty rushes to the transporter controls."))
4335         if game.shldup:
4336             prout(_("But with the shields up it's hopeless."))
4337             finish(FPNOVA)
4338         prouts(_("His desperate attempt to rescue you . . ."))
4339         if rnd.withprob(0.5):
4340             prout(_("fails."))
4341             finish(FPNOVA)
4342             return
4343         prout(_("SUCCEEDS!"))
4344         if game.imine:
4345             game.imine = False
4346             proutn(_("The crystals mined were "))
4347             if rnd.withprob(0.25):
4348                 prout(_("lost."))
4349             else:
4350                 prout(_("saved."))
4351                 game.icrystl = True
4352     if igrab:
4353         return
4354     # Check to see if captain in shuttle craft
4355     if game.icraft:
4356         finish(FSTRACTOR)
4357     if game.alldone:
4358         return
4359     # Inform captain of attempt to reach safety
4360     skip(1)
4361     while True:
4362         if game.justin:
4363             prouts(_("***RED ALERT!  RED ALERT!"))
4364             skip(1)
4365             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4366             prouts(_("   a supernova."))
4367             skip(2)
4368         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4369         prout(_("safely out of quadrant."))
4370         if not damaged(DRADIO):
4371             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4372         # Try to use warp engines
4373         if damaged(DWARPEN):
4374             skip(1)
4375             prout(_("Warp engines damaged."))
4376             finish(FSNOVAED)
4377             return
4378         game.warpfac = rnd.real(6.0, 8.0)
4379         prout(_("Warp factor set to %d") % int(game.warpfac))
4380         power = 0.75*game.energy
4381         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4382         dist = max(dist, rnd.real(math.sqrt(2)))
4383         bugout = course(bearing=rnd.real(12), distance=dist)        # How dumb!
4384         game.optime = bugout.time(game.warpfac)
4385         game.justin = False
4386         game.inorbit = False
4387         warp(bugout, involuntary=True)
4388         if not game.justin:
4389             # This is bad news, we didn't leave quadrant.
4390             if game.alldone:
4391                 return
4392             skip(1)
4393             prout(_("Insufficient energy to leave quadrant."))
4394             finish(FSNOVAED)
4395             return
4396         # Repeat if another snova
4397         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4398             break
4399     if game.unwon()==0:
4400         finish(FWON) # Snova killed remaining enemy.
4401
4402 def timwrp():
4403     "Let's do the time warp again."
4404     prout(_("***TIME WARP ENTERED."))
4405     if game.state.snap and rnd.withprob(0.5):
4406         # Go back in time
4407         prout(_("You are traveling backwards in time %d stardates.") %
4408               int(game.state.date-game.snapsht.date))
4409         game.state = game.snapsht
4410         game.state.snap = False
4411         if len(game.state.kcmdr):
4412             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4413             schedule(FBATTAK, expran(0.3*game.intime))
4414         schedule(FSNOVA, expran(0.5*game.intime))
4415         # next snapshot will be sooner
4416         schedule(FSNAP, expran(0.25*game.state.remtime))
4417
4418         if game.state.nscrem:
4419             schedule(FSCMOVE, 0.2777)
4420         game.isatb = 0
4421         unschedule(FCDBAS)
4422         unschedule(FSCDBAS)
4423         game.battle.invalidate()
4424         # Make sure Galileo is consistant -- Snapshot may have been taken
4425         # when on planet, which would give us two Galileos!
4426         gotit = False
4427         for l in range(game.inplan):
4428             if game.state.planets[l].known == "shuttle_down":
4429                 gotit = True
4430                 if game.iscraft == "onship" and game.ship=='E':
4431                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4432                     game.iscraft = "offship"
4433         # Likewise, if in the original time the Galileo was abandoned, but
4434         # was on ship earlier, it would have vanished -- let's restore it.
4435         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4436             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4437             game.iscraft = "onship"
4438         # There used to be code to do the actual reconstrction here,
4439         # but the starchart is now part of the snapshotted galaxy state.
4440         prout(_("Spock has reconstructed a correct star chart from memory"))
4441     else:
4442         # Go forward in time
4443         game.optime = expran(0.5*game.intime)
4444         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4445         # cheat to make sure no tractor beams occur during time warp
4446         postpone(FTBEAM, game.optime)
4447         game.damage[DRADIO] += game.optime
4448     newqad()
4449     events()        # Stas Sergeev added this -- do pending events
4450
4451 def probe():
4452     "Launch deep-space probe."
4453     # New code to launch a deep space probe
4454     if game.nprobes == 0:
4455         scanner.chew()
4456         skip(1)
4457         if game.ship == 'E':
4458             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4459         else:
4460             prout(_("Ye Faerie Queene has no deep space probes."))
4461         return
4462     if damaged(DDSP):
4463         scanner.chew()
4464         skip(1)
4465         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4466         return
4467     if is_scheduled(FDSPROB):
4468         scanner.chew()
4469         skip(1)
4470         if damaged(DRADIO) and game.condition != "docked":
4471             prout(_("Spock-  \"Records show the previous probe has not yet"))
4472             prout(_("   reached its destination.\""))
4473         else:
4474             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4475         return
4476     key = scanner.nexttok()
4477     if key == "IHEOL":
4478         if game.nprobes == 1:
4479             prout(_("1 probe left."))
4480         else:
4481             prout(_("%d probes left") % game.nprobes)
4482         proutn(_("Are you sure you want to fire a probe? "))
4483         if not ja():
4484             return
4485     game.isarmed = False
4486     if key == "IHALPHA" and scanner.token == "armed":
4487         game.isarmed = True
4488         key = scanner.nexttok()
4489     elif key == "IHEOL":
4490         proutn(_("Arm NOVAMAX warhead? "))
4491         game.isarmed = ja()
4492     elif key == "IHREAL":                # first element of course
4493         scanner.push(scanner.token)
4494     try:
4495         game.probe = getcourse(isprobe=True)
4496     except TrekError:
4497         return
4498     game.nprobes -= 1
4499     schedule(FDSPROB, 0.01) # Time to move one sector
4500     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4501     game.ididit = True
4502     return
4503
4504 def mayday():
4505     "Yell for help from nearest starbase."
4506     # There's more than one way to move in this game!
4507     scanner.chew()
4508     # Test for conditions which prevent calling for help
4509     if game.condition == "docked":
4510         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4511         return
4512     if damaged(DRADIO):
4513         prout(_("Subspace radio damaged."))
4514         return
4515     if not game.state.baseq:
4516         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4517         return
4518     if game.landed:
4519         prout(_("You must be aboard the %s.") % crmshp())
4520         return
4521     # OK -- call for help from nearest starbase
4522     game.nhelp += 1
4523     if game.base.i!=0:
4524         # There's one in this quadrant
4525         ddist = (game.base - game.sector).distance()
4526     else:
4527         ibq = None      # Force base-quadrant game to persist past loop
4528         ddist = FOREVER
4529         for ibq in game.state.baseq:
4530             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4531             if xdist < ddist:
4532                 ddist = xdist
4533         if ibq is None:
4534             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4535             return
4536         # Since starbase not in quadrant, set up new quadrant
4537         game.quadrant = ibq
4538         newqad()
4539     # dematerialize starship
4540     game.quad[game.sector.i][game.sector.j]='.'
4541     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4542            % (game.quadrant, crmshp()))
4543     game.sector.invalidate()
4544     for m in range(1, 5+1):
4545         w = game.base.scatter()
4546         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4547             # found one -- finish up
4548             game.sector = w
4549             break
4550     if game.sector is None:
4551         prout(_("You have been lost in space..."))
4552         finish(FMATERIALIZE)
4553         return
4554     # Give starbase three chances to rematerialize starship
4555     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4556     for m in range(1, 3+1):
4557         if m == 1: proutn(_("1st"))
4558         elif m == 2: proutn(_("2nd"))
4559         elif m == 3: proutn(_("3rd"))
4560         proutn(_(" attempt to re-materialize ") + crmshp())
4561         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4562         textcolor(RED)
4563         warble()
4564         if rnd.real() > probf:
4565             break
4566         prout(_("fails."))
4567         textcolor(DEFAULT)
4568         curses.delay_output(500)
4569     if m > 3:
4570         game.quad[game.sector.i][game.sector.j]='?'
4571         game.alive = False
4572         drawmaps(1)
4573         setwnd(message_window)
4574         finish(FMATERIALIZE)
4575         return
4576     game.quad[game.sector.i][game.sector.j]=game.ship
4577     textcolor(GREEN)
4578     prout(_("succeeds."))
4579     textcolor(DEFAULT)
4580     dock(False)
4581     skip(1)
4582     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4583
4584 def abandon():
4585     "Abandon ship."
4586     scanner.chew()
4587     if game.condition=="docked":
4588         if game.ship!='E':
4589             prout(_("You cannot abandon Ye Faerie Queene."))
4590             return
4591     else:
4592         # Must take shuttle craft to exit
4593         if game.damage[DSHUTTL]==-1:
4594             prout(_("Ye Faerie Queene has no shuttle craft."))
4595             return
4596         if game.damage[DSHUTTL]<0:
4597             prout(_("Shuttle craft now serving Big Macs."))
4598             return
4599         if game.damage[DSHUTTL]>0:
4600             prout(_("Shuttle craft damaged."))
4601             return
4602         if game.landed:
4603             prout(_("You must be aboard the ship."))
4604             return
4605         if game.iscraft != "onship":
4606             prout(_("Shuttle craft not currently available."))
4607             return
4608         # Emit abandon ship messages
4609         skip(1)
4610         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4611         skip(1)
4612         prouts(_("***ALL HANDS ABANDON SHIP!"))
4613         skip(2)
4614         prout(_("Captain and crew escape in shuttle craft."))
4615         if not game.state.baseq:
4616             # Oops! no place to go...
4617             finish(FABANDN)
4618             return
4619         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4620         # Dispose of crew
4621         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4622             prout(_("Remainder of ship's complement beam down"))
4623             prout(_("to nearest habitable planet."))
4624         elif q.planet is not None and not damaged(DTRANSP):
4625             prout(_("Remainder of ship's complement beam down to %s.") %
4626                   q.planet)
4627         else:
4628             prout(_("Entire crew of %d left to die in outer space.") %
4629                   game.state.crew)
4630             game.casual += game.state.crew
4631             game.abandoned += game.state.crew
4632         # If at least one base left, give 'em the Faerie Queene
4633         skip(1)
4634         game.icrystl = False # crystals are lost
4635         game.nprobes = 0 # No probes
4636         prout(_("You are captured by Klingons and released to"))
4637         prout(_("the Federation in a prisoner-of-war exchange."))
4638         nb = rnd.integer(len(game.state.baseq))
4639         # Set up quadrant and position FQ adjacient to base
4640         if not game.quadrant == game.state.baseq[nb]:
4641             game.quadrant = game.state.baseq[nb]
4642             game.sector.i = game.sector.j = 5
4643             newqad()
4644         while True:
4645             # position next to base by trial and error
4646             game.quad[game.sector.i][game.sector.j] = '.'
4647             l = QUADSIZE
4648             for l in range(QUADSIZE):
4649                 game.sector = game.base.scatter()
4650                 if game.sector.valid_sector() and \
4651                        game.quad[game.sector.i][game.sector.j] == '.':
4652                     break
4653             if l < QUADSIZE:
4654                 break # found a spot
4655             game.sector.i=QUADSIZE/2
4656             game.sector.j=QUADSIZE/2
4657             newqad()
4658     # Get new commission
4659     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4660     game.state.crew = FULLCREW
4661     prout(_("Starfleet puts you in command of another ship,"))
4662     prout(_("the Faerie Queene, which is antiquated but,"))
4663     prout(_("still useable."))
4664     if game.icrystl:
4665         prout(_("The dilithium crystals have been moved."))
4666     game.imine = False
4667     game.iscraft = "offship" # Galileo disappears
4668     # Resupply ship
4669     game.condition="docked"
4670     for l in range(NDEVICES):
4671         game.damage[l] = 0.0
4672     game.damage[DSHUTTL] = -1
4673     game.energy = game.inenrg = 3000.0
4674     game.shield = game.inshld = 1250.0
4675     game.torps = game.intorps = 6
4676     game.lsupres=game.inlsr=3.0
4677     game.shldup=False
4678     game.warpfac=5.0
4679     game.brigfree = game.brigcapacity = 300
4680     return
4681
4682 # Code from planets.c begins here.
4683
4684 def consumeTime():
4685     "Abort a lengthy operation if an event interrupts it."
4686     game.ididit = True
4687     events()
4688     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4689         return True
4690     return False
4691
4692 def survey():
4693     "Report on (uninhabited) planets in the galaxy."
4694     iknow = False
4695     skip(1)
4696     scanner.chew()
4697     prout(_("Spock-  \"Planet report follows, Captain.\""))
4698     skip(1)
4699     for i in range(game.inplan):
4700         if game.state.planets[i].pclass == "destroyed":
4701             continue
4702         if (game.state.planets[i].known != "unknown" \
4703             and not game.state.planets[i].inhabited) \
4704             or game.idebug:
4705             iknow = True
4706             if game.idebug and game.state.planets[i].known=="unknown":
4707                 proutn("(Unknown) ")
4708             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4709             proutn(_("   class "))
4710             proutn(game.state.planets[i].pclass)
4711             proutn("   ")
4712             if game.state.planets[i].crystals != "present":
4713                 proutn(_("no "))
4714             prout(_("dilithium crystals present."))
4715             if game.state.planets[i].known=="shuttle_down":
4716                 prout(_("    Shuttle Craft Galileo on surface."))
4717     if not iknow:
4718         prout(_("No information available."))
4719
4720 def orbit():
4721     "Enter standard orbit."
4722     skip(1)
4723     scanner.chew()
4724     if game.inorbit:
4725         prout(_("Already in standard orbit."))
4726         return
4727     if damaged(DWARPEN) and damaged(DIMPULS):
4728         prout(_("Both warp and impulse engines damaged."))
4729         return
4730     if game.plnet is None:
4731         prout("There is no planet in this sector.")
4732         return
4733     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4734         prout(crmshp() + _(" not adjacent to planet."))
4735         skip(1)
4736         return
4737     game.optime = rnd.real(0.02, 0.05)
4738     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4739     newcnd()
4740     if consumeTime():
4741         return
4742     game.height = rnd.real(1400, 8600)
4743     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4744     game.inorbit = True
4745     game.ididit = True
4746
4747 def sensor():
4748     "Examine planets in this quadrant."
4749     if damaged(DSRSENS):
4750         if game.options & OPTION_TTY:
4751             prout(_("Short range sensors damaged."))
4752         return
4753     if game.iplnet is None:
4754         if game.options & OPTION_TTY:
4755             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4756         return
4757     if game.iplnet.known == "unknown":
4758         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4759         skip(1)
4760         prout(_("         Planet at Sector %s is of class %s.") %
4761               (game.plnet, game.iplnet.pclass))
4762         if game.iplnet.known=="shuttle_down":
4763             prout(_("         Sensors show Galileo still on surface."))
4764         proutn(_("         Readings indicate"))
4765         if game.iplnet.crystals != "present":
4766             proutn(_(" no"))
4767         prout(_(" dilithium crystals present.\""))
4768         if game.iplnet.known == "unknown":
4769             game.iplnet.known = "known"
4770     elif game.iplnet.inhabited:
4771         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4772         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4773
4774 def beam():
4775     "Use the transporter."
4776     nrgneed = 0
4777     scanner.chew()
4778     skip(1)
4779     if damaged(DTRANSP):
4780         prout(_("Transporter damaged."))
4781         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4782             skip(1)
4783             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4784             if ja():
4785                 shuttle()
4786         return
4787     if not game.inorbit:
4788         prout(crmshp() + _(" not in standard orbit."))
4789         return
4790     if game.shldup:
4791         prout(_("Impossible to transport through shields."))
4792         return
4793     if game.iplnet.known=="unknown":
4794         prout(_("Spock-  \"Captain, we have no information on this planet"))
4795         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4796         prout(_("  you may not go down.\""))
4797         return
4798     if not game.landed and game.iplnet.crystals=="absent":
4799         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4800         prout(_("  exploring a planet with no dilithium crystals."))
4801         proutn(_("  Are you sure this is wise?\" "))
4802         if not ja():
4803             scanner.chew()
4804             return
4805     if not (game.options & OPTION_PLAIN):
4806         nrgneed = 50 * game.skill + game.height / 100.0
4807         if nrgneed > game.energy:
4808             prout(_("Engineering to bridge--"))
4809             prout(_("  Captain, we don't have enough energy for transportation."))
4810             return
4811         if not game.landed and nrgneed * 2 > game.energy:
4812             prout(_("Engineering to bridge--"))
4813             prout(_("  Captain, we have enough energy only to transport you down to"))
4814             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4815             if game.iplnet.known == "shuttle_down":
4816                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4817             proutn(_("  Are you sure this is wise?\" "))
4818             if not ja():
4819                 scanner.chew()
4820                 return
4821     if game.landed:
4822         # Coming from planet
4823         if game.iplnet.known=="shuttle_down":
4824             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4825             if ja():
4826                 scanner.chew()
4827                 return
4828             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4829         prout(_("Landing party assembled, ready to beam up."))
4830         skip(1)
4831         prout(_("Kirk whips out communicator..."))
4832         prouts(_("BEEP  BEEP  BEEP"))
4833         skip(2)
4834         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4835     else:
4836         # Going to planet
4837         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4838         skip(1)
4839         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4840         skip(1)
4841         prout(_("Kirk-  \"Energize.\""))
4842     game.ididit = True
4843     skip(1)
4844     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4845     skip(2)
4846     if not rnd.withprob(0.98):
4847         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4848         skip(2)
4849         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4850         finish(FLOST)
4851         return
4852     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4853     game.landed = not game.landed
4854     game.energy -= nrgneed
4855     skip(2)
4856     prout(_("Transport complete."))
4857     if game.landed and game.iplnet.known=="shuttle_down":
4858         prout(_("The shuttle craft Galileo is here!"))
4859     if not game.landed and game.imine:
4860         game.icrystl = True
4861         game.cryprob = 0.05
4862     game.imine = False
4863     return
4864
4865 def mine():
4866     "Strip-mine a world for dilithium."
4867     skip(1)
4868     scanner.chew()
4869     if not game.landed:
4870         prout(_("Mining party not on planet."))
4871         return
4872     if game.iplnet.crystals == "mined":
4873         prout(_("This planet has already been strip-mined for dilithium."))
4874         return
4875     elif game.iplnet.crystals == "absent":
4876         prout(_("No dilithium crystals on this planet."))
4877         return
4878     if game.imine:
4879         prout(_("You've already mined enough crystals for this trip."))
4880         return
4881     if game.icrystl and game.cryprob == 0.05:
4882         prout(_("With all those fresh crystals aboard the ") + crmshp())
4883         prout(_("there's no reason to mine more at this time."))
4884         return
4885     game.optime = rnd.real(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4886     if consumeTime():
4887         return
4888     prout(_("Mining operation complete."))
4889     game.iplnet.crystals = "mined"
4890     game.imine = game.ididit = True
4891
4892 def usecrystals():
4893     "Use dilithium crystals."
4894     game.ididit = False
4895     skip(1)
4896     scanner.chew()
4897     if not game.icrystl:
4898         prout(_("No dilithium crystals available."))
4899         return
4900     if game.energy >= 1000:
4901         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4902         prout(_("  except when Condition Yellow exists."))
4903         return
4904     prout(_("Spock- \"Captain, I must warn you that loading"))
4905     prout(_("  raw dilithium crystals into the ship's power"))
4906     prout(_("  system may risk a severe explosion."))
4907     proutn(_("  Are you sure this is wise?\" "))
4908     if not ja():
4909         scanner.chew()
4910         return
4911     skip(1)
4912     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4913     prout(_("  Mr. Spock and I will try it.\""))
4914     skip(1)
4915     prout(_("Spock-  \"Crystals in place, Sir."))
4916     prout(_("  Ready to activate circuit.\""))
4917     skip(1)
4918     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4919     skip(1)
4920     if rnd.withprob(game.cryprob):
4921         prouts(_("  \"Activating now! - - No good!  It's***"))
4922         skip(2)
4923         prouts(_("***RED ALERT!  RED A*L********************************"))
4924         skip(1)
4925         stars()
4926         prouts(_("******************   KA-BOOM!!!!   *******************"))
4927         skip(1)
4928         kaboom()
4929         return
4930     game.energy += rnd.real(5000.0, 5500.0)
4931     prouts(_("  \"Activating now! - - "))
4932     prout(_("The instruments"))
4933     prout(_("   are going crazy, but I think it's"))
4934     prout(_("   going to work!!  Congratulations, Sir!\""))
4935     game.cryprob *= 2.0
4936     game.ididit = True
4937
4938 def shuttle():
4939     "Use shuttlecraft for planetary jaunt."
4940     scanner.chew()
4941     skip(1)
4942     if damaged(DSHUTTL):
4943         if game.damage[DSHUTTL] == -1.0:
4944             if game.inorbit and game.iplnet.known == "shuttle_down":
4945                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4946             else:
4947                 prout(_("Ye Faerie Queene had no shuttle craft."))
4948         elif game.damage[DSHUTTL] > 0:
4949             prout(_("The Galileo is damaged."))
4950         else: # game.damage[DSHUTTL] < 0
4951             prout(_("Shuttle craft is now serving Big Macs."))
4952         return
4953     if not game.inorbit:
4954         prout(crmshp() + _(" not in standard orbit."))
4955         return
4956     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4957         prout(_("Shuttle craft not currently available."))
4958         return
4959     if not game.landed and game.iplnet.known=="shuttle_down":
4960         prout(_("You will have to beam down to retrieve the shuttle craft."))
4961         return
4962     if game.shldup or game.condition == "docked":
4963         prout(_("Shuttle craft cannot pass through shields."))
4964         return
4965     if game.iplnet.known=="unknown":
4966         prout(_("Spock-  \"Captain, we have no information on this planet"))
4967         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4968         prout(_("  you may not fly down.\""))
4969         return
4970     game.optime = 3.0e-5*game.height
4971     if game.optime >= 0.8*game.state.remtime:
4972         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4973         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4974                int(100*game.optime/game.state.remtime))
4975         prout(_("remaining time."))
4976         proutn(_("Are you sure this is wise?\" "))
4977         if not ja():
4978             game.optime = 0.0
4979             return
4980     if game.landed:
4981         # Kirk on planet
4982         if game.iscraft == "onship":
4983             # Galileo on ship!
4984             if not damaged(DTRANSP):
4985                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4986                 if ja():
4987                     beam()
4988                     return
4989                 proutn(_("Shuttle crew"))
4990             else:
4991                 proutn(_("Rescue party"))
4992             prout(_(" boards Galileo and swoops toward planet surface."))
4993             game.iscraft = "offship"
4994             skip(1)
4995             if consumeTime():
4996                 return
4997             game.iplnet.known="shuttle_down"
4998             prout(_("Trip complete."))
4999             return
5000         else:
5001             # Ready to go back to ship
5002             prout(_("You and your mining party board the"))
5003             prout(_("shuttle craft for the trip back to the Enterprise."))
5004             skip(1)
5005             prouts(_("The short hop begins . . ."))
5006             skip(1)
5007             game.iplnet.known="known"
5008             game.icraft = True
5009             skip(1)
5010             game.landed = False
5011             if consumeTime():
5012                 return
5013             game.iscraft = "onship"
5014             game.icraft = False
5015             if game.imine:
5016                 game.icrystl = True
5017                 game.cryprob = 0.05
5018             game.imine = False
5019             prout(_("Trip complete."))
5020             return
5021     else:
5022         # Kirk on ship and so is Galileo
5023         prout(_("Mining party assembles in the hangar deck,"))
5024         prout(_("ready to board the shuttle craft \"Galileo\"."))
5025         skip(1)
5026         prouts(_("The hangar doors open; the trip begins."))
5027         skip(1)
5028         game.icraft = True
5029         game.iscraft = "offship"
5030         if consumeTime():
5031             return
5032         game.iplnet.known = "shuttle_down"
5033         game.landed = True
5034         game.icraft = False
5035         prout(_("Trip complete."))
5036         return
5037
5038 def deathray():
5039     "Use the big zapper."
5040     game.ididit = False
5041     skip(1)
5042     scanner.chew()
5043     if game.ship != 'E':
5044         prout(_("Ye Faerie Queene has no death ray."))
5045         return
5046     if len(game.enemies)==0:
5047         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
5048         return
5049     if damaged(DDRAY):
5050         prout(_("Death Ray is damaged."))
5051         return
5052     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
5053     prout(_("  is highly unpredictible.  Considering the alternatives,"))
5054     proutn(_("  are you sure this is wise?\" "))
5055     if not ja():
5056         return
5057     prout(_("Spock-  \"Acknowledged.\""))
5058     skip(1)
5059     game.ididit = True
5060     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
5061     skip(1)
5062     prout(_("Crew scrambles in emergency preparation."))
5063     prout(_("Spock and Scotty ready the death ray and"))
5064     prout(_("prepare to channel all ship's power to the device."))
5065     skip(1)
5066     prout(_("Spock-  \"Preparations complete, sir.\""))
5067     prout(_("Kirk-  \"Engage!\""))
5068     skip(1)
5069     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5070     skip(1)
5071     dprob = 0.30
5072     if game.options & OPTION_PLAIN:
5073         dprob = 0.5
5074     r = rnd.real()
5075     if r > dprob:
5076         prouts(_("Sulu- \"Captain!  It's working!\""))
5077         skip(2)
5078         while len(game.enemies) > 0:
5079             deadkl(game.enemies[-1].location, game.quad[game.enemies[-1].location.i][game.enemies[-1].location.j],game.enemies[-1].location)
5080         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5081         if game.unwon() == 0:
5082             finish(FWON)
5083         if (game.options & OPTION_PLAIN) == 0:
5084             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5085             if rnd.withprob(0.05):
5086                 prout(_("   is still operational.\""))
5087             else:
5088                 prout(_("   has been rendered nonfunctional.\""))
5089                 game.damage[DDRAY] = 39.95
5090         return
5091     r = rnd.real()        # Pick failure method
5092     if r <= 0.30:
5093         prouts(_("Sulu- \"Captain!  It's working!\""))
5094         skip(1)
5095         prouts(_("***RED ALERT!  RED ALERT!"))
5096         skip(1)
5097         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5098         skip(1)
5099         prouts(_("***RED ALERT!  RED A*L********************************"))
5100         skip(1)
5101         stars()
5102         prouts(_("******************   KA-BOOM!!!!   *******************"))
5103         skip(1)
5104         kaboom()
5105         return
5106     if r <= 0.55:
5107         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5108         skip(1)
5109         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5110         skip(1)
5111         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5112         prout(_("  have apparently been transformed into strange mutations."))
5113         prout(_("  Vulcans do not seem to be affected."))
5114         skip(1)
5115         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5116         finish(FDRAY)
5117         return
5118     if r <= 0.75:
5119         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5120         skip(2)
5121         proutn(_("Spock-  \"I believe the word is"))
5122         prouts(_(" *ASTONISHING*"))
5123         prout(_(" Mr. Sulu."))
5124         for i in range(QUADSIZE):
5125             for j in range(QUADSIZE):
5126                 if game.quad[i][j] == '.':
5127                     game.quad[i][j] = '?'
5128         prout(_("  Captain, our quadrant is now infested with"))
5129         prouts(_(" - - - - - -  *THINGS*."))
5130         skip(1)
5131         prout(_("  I have no logical explanation.\""))
5132         return
5133     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5134     skip(1)
5135     prout(_("Scotty-  \"There are so many tribbles down here"))
5136     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5137     finish(FTRIBBLE)
5138     return
5139
5140 # Code from reports.c begins here
5141
5142 def attackreport(curt):
5143     "eport status of bases under attack."
5144     if not curt:
5145         if is_scheduled(FCDBAS):
5146             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5147             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5148         elif game.isatb == 1:
5149             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5150             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5151         else:
5152             prout(_("No Starbase is currently under attack."))
5153     else:
5154         if is_scheduled(FCDBAS):
5155             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5156         if game.isatb:
5157             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5158         clreol()
5159
5160 def report():
5161     # report on general game status
5162     scanner.chew()
5163     s1 = (game.thawed and _("thawed ")) or ""
5164     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5165     s3 = (None, _("novice"), _("fair"),
5166           _("good"), _("expert"), _("emeritus"))[game.skill]
5167     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5168     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5169         prout(_("No plaque is allowed."))
5170     if game.tourn:
5171         prout(_("This is tournament game %d.") % game.tourn)
5172     prout(_("Your secret password is \"%s\"") % game.passwd)
5173     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5174                                                       (game.inkling + game.incom + game.inscom)))
5175     if game.incom - len(game.state.kcmdr):
5176         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5177     elif game.inkling - game.remkl() + (game.inscom - game.state.nscrem) > 0:
5178         prout(_(", but no Commanders."))
5179     else:
5180         prout(".")
5181     if game.skill > SKILL_FAIR:
5182         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5183     if len(game.state.baseq) != game.inbase:
5184         proutn(_("There "))
5185         if game.inbase-len(game.state.baseq)==1:
5186             proutn(_("has been 1 base"))
5187         else:
5188             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5189         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5190     else:
5191         prout(_("There are %d bases.") % game.inbase)
5192     if communicating() or game.iseenit:
5193         # Don't report this if not seen and
5194         # either the radio is dead or not at base!
5195         attackreport(False)
5196         game.iseenit = True
5197     if game.casual:
5198         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5199     if game.brigcapacity != game.brigfree:
5200         embriggened = game.brigcapacity-game.brigfree
5201         if embriggened == 1:
5202             prout(_("1 Klingon in brig"))
5203         else:
5204             prout(_("%d Klingons in brig.") %  embriggened)
5205         if game.kcaptured == 0:
5206             pass
5207         elif game.kcaptured == 1:
5208             prout(_("1 captured Klingon turned in to Starfleet."))
5209         else:
5210             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5211     if game.nhelp:
5212         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5213     if game.ship == 'E':
5214         proutn(_("You have "))
5215         if game.nprobes:
5216             proutn("%d" % (game.nprobes))
5217         else:
5218             proutn(_("no"))
5219         proutn(_(" deep space probe"))
5220         if game.nprobes!=1:
5221             proutn(_("s"))
5222         prout(".")
5223     if communicating() and is_scheduled(FDSPROB):
5224         if game.isarmed:
5225             proutn(_("An armed deep space probe is in "))
5226         else:
5227             proutn(_("A deep space probe is in "))
5228         prout("Quadrant %s." % game.probe.quadrant())
5229     if game.icrystl:
5230         if game.cryprob <= .05:
5231             prout(_("Dilithium crystals aboard ship... not yet used."))
5232         else:
5233             i=0
5234             ai = 0.05
5235             while game.cryprob > ai:
5236                 ai *= 2.0
5237                 i += 1
5238             prout(_("Dilithium crystals have been used %d time%s.") % \
5239                   (i, (_("s"), "")[i==1]))
5240     skip(1)
5241
5242 def lrscan(silent):
5243     "Long-range sensor scan."
5244     if damaged(DLRSENS):
5245         # Now allow base's sensors if docked
5246         if game.condition != "docked":
5247             if not silent:
5248                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5249             return
5250         if not silent:
5251             prout(_("Starbase's long-range scan"))
5252     elif not silent:
5253         prout(_("Long-range scan"))
5254     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5255         if not silent:
5256             proutn(" ")
5257         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5258             if not Coord(x, y).valid_quadrant():
5259                 if not silent:
5260                     proutn("  -1")
5261             else:
5262                 if not damaged(DRADIO):
5263                     game.state.galaxy[x][y].charted = True
5264                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5265                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5266                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5267                 if not silent and game.state.galaxy[x][y].supernova:
5268                     proutn(" ***")
5269                 elif not silent:
5270                     cn = " %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars)
5271                     proutn(((3 - len(cn)) * '.') + cn)
5272         if not silent:
5273             prout(" ")
5274
5275 def damagereport():
5276     "Damage report."
5277     jdam = False
5278     scanner.chew()
5279     for i in range(NDEVICES):
5280         if damaged(i):
5281             if not jdam:
5282                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5283                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5284                 jdam = True
5285             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5286                                                game.damage[i]+0.05,
5287                                                DOCKFAC*game.damage[i]+0.005))
5288     if not jdam:
5289         prout(_("All devices functional."))
5290
5291 def rechart():
5292     "Update the chart in the Enterprise's computer from galaxy data."
5293     game.lastchart = game.state.date
5294     for i in range(GALSIZE):
5295         for j in range(GALSIZE):
5296             if game.state.galaxy[i][j].charted:
5297                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5298                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5299                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5300
5301 def chart():
5302     "Display the star chart."
5303     scanner.chew()
5304     if (game.options & OPTION_AUTOSCAN):
5305         lrscan(silent=True)
5306     if communicating():
5307         rechart()
5308     if game.lastchart < game.state.date and game.condition == "docked":
5309         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5310         rechart()
5311     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5312     if game.state.date > game.lastchart:
5313         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5314     prout("      1    2    3    4    5    6    7    8")
5315     for i in range(GALSIZE):
5316         proutn("%d |" % (i+1))
5317         for j in range(GALSIZE):
5318             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5319                 proutn("<")
5320             else:
5321                 proutn(" ")
5322             if game.state.galaxy[i][j].supernova:
5323                 show = "***"
5324             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5325                 show = ".1."
5326             elif game.state.galaxy[i][j].charted:
5327                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5328                 if (game.options & OPTION_DOTFILL):
5329                     show = show.replace(" ", ".")
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 | OPTION_DOTFILL)
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 | OPTION_DOTFILL)
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     game.tholian = None
6061     # Create a blank quadrant
6062     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6063     if game.iscate:
6064         # Attempt to escape Super-commander, so tbeam back!
6065         game.iscate = False
6066         game.ientesc = True
6067     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6068     # cope with supernova
6069     if q.supernova:
6070         return
6071     game.klhere = q.klingons
6072     game.irhere = q.romulans
6073     # Position Starship
6074     game.quad[game.sector.i][game.sector.j] = game.ship
6075     game.enemies = []
6076     if q.klingons:
6077         # Position ordinary Klingons
6078         for _i in range(game.klhere):
6079             newkling()
6080         # If we need a commander, promote a Klingon
6081         for cmdr in game.state.kcmdr:
6082             if cmdr == game.quadrant:
6083                 e = game.enemies[game.klhere-1]
6084                 game.quad[e.location.i][e.location.j] = 'C'
6085                 e.power = rnd.real(950,1350) + 50.0*game.skill
6086                 break
6087         # If we need a super-commander, promote a Klingon
6088         if game.quadrant == game.state.kscmdr:
6089             e = game.enemies[0]
6090             game.quad[e.location.i][e.location.j] = 'S'
6091             e.power = rnd.real(1175.0,  1575.0) + 125.0*game.skill
6092             game.iscate = (game.remkl() > 1)
6093     # Put in Romulans if needed
6094     for _i in range(q.romulans):
6095         Enemy('R', loc=dropin(), power=rnd.real(400.0,850.0)+50.0*game.skill)
6096     # If quadrant needs a starbase, put it in
6097     if q.starbase:
6098         game.base = dropin('B')
6099     # If quadrant needs a planet, put it in
6100     if q.planet:
6101         game.iplnet = q.planet
6102         if not q.planet.inhabited:
6103             game.plnet = dropin('P')
6104         else:
6105             game.plnet = dropin('@')
6106     # Check for condition
6107     newcnd()
6108     # Check for RNZ
6109     if game.irhere > 0 and game.klhere == 0:
6110         game.neutz = True
6111         if not damaged(DRADIO):
6112             skip(1)
6113             prout(_("LT. Uhura- \"Captain, an urgent message."))
6114             prout(_("  I'll put it on audio.\"  CLICK"))
6115             skip(1)
6116             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6117             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6118     # Put in THING if needed
6119     if thing == game.quadrant:
6120         Enemy(etype='?', loc=dropin(),
6121               power=rnd.real(6000,6500.0)+250.0*game.skill)
6122         if not damaged(DSRSENS):
6123             skip(1)
6124             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6125             prout(_("    Please examine your short-range scan.\""))
6126     # Decide if quadrant needs a Tholian; lighten up if skill is low
6127     if game.options & OPTION_THOLIAN:
6128         if (game.skill < SKILL_GOOD and rnd.withprob(0.02)) or \
6129             (game.skill == SKILL_GOOD and rnd.withprob(0.05)) or \
6130             (game.skill > SKILL_GOOD and rnd.withprob(0.08)):
6131             w = Coord()
6132             while True:
6133                 w.i = rnd.withprob(0.5) * (QUADSIZE-1)
6134                 w.j = rnd.withprob(0.5) * (QUADSIZE-1)
6135                 if game.quad[w.i][w.j] == '.':
6136                     break
6137             game.tholian = Enemy(etype='T', loc=w,
6138                                  power=rnd.integer(100, 500) + 25.0*game.skill)
6139             # Reserve unoccupied corners
6140             if game.quad[0][0]=='.':
6141                 game.quad[0][0] = 'X'
6142             if game.quad[0][QUADSIZE-1]=='.':
6143                 game.quad[0][QUADSIZE-1] = 'X'
6144             if game.quad[QUADSIZE-1][0]=='.':
6145                 game.quad[QUADSIZE-1][0] = 'X'
6146             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6147                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6148     sortenemies()
6149     # And finally the stars
6150     for _i in range(q.stars):
6151         dropin('*')
6152     # Put in a few black holes
6153     for _i in range(1, 3+1):
6154         if rnd.withprob(0.5):
6155             dropin(' ')
6156     # Take out X's in corners if Tholian present
6157     if game.tholian:
6158         if game.quad[0][0]=='X':
6159             game.quad[0][0] = '.'
6160         if game.quad[0][QUADSIZE-1]=='X':
6161             game.quad[0][QUADSIZE-1] = '.'
6162         if game.quad[QUADSIZE-1][0]=='X':
6163             game.quad[QUADSIZE-1][0] = '.'
6164         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6165             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6166     # This should guarantee that replay games don't lose info about the chart
6167     if (game.options & OPTION_AUTOSCAN) or replayfp:
6168         lrscan(silent=True)
6169
6170 def setpassword():
6171     "Set the self-destruct password."
6172     if game.options & OPTION_PLAIN:
6173         while True:
6174             scanner.chew()
6175             proutn(_("Please type in a secret password- "))
6176             scanner.nexttok()
6177             game.passwd = scanner.token
6178             if game.passwd is not None:
6179                 break
6180     else:
6181         game.passwd = ""
6182         game.passwd += chr(ord('a')+rnd.integer(26))
6183         game.passwd += chr(ord('a')+rnd.integer(26))
6184         game.passwd += chr(ord('a')+rnd.integer(26))
6185
6186 # Code from sst.c begins here
6187
6188 commands = [
6189     ("SRSCAN",           OPTION_TTY),
6190     ("STATUS",           OPTION_TTY),
6191     ("REQUEST",          OPTION_TTY),
6192     ("LRSCAN",           OPTION_TTY),
6193     ("PHASERS",          0),
6194     ("TORPEDO",          0),
6195     ("PHOTONS",          0),
6196     ("MOVE",             0),
6197     ("SHIELDS",          0),
6198     ("DOCK",             0),
6199     ("DAMAGES",          0),
6200     ("CHART",            0),
6201     ("IMPULSE",          0),
6202     ("REST",             0),
6203     ("WARP",             0),
6204     ("SENSORS",          OPTION_PLANETS),
6205     ("ORBIT",            OPTION_PLANETS),
6206     ("TRANSPORT",        OPTION_PLANETS),
6207     ("MINE",             OPTION_PLANETS),
6208     ("CRYSTALS",         OPTION_PLANETS),
6209     ("SHUTTLE",          OPTION_PLANETS),
6210     ("PLANETS",          OPTION_PLANETS),
6211     ("REPORT",           0),
6212     ("COMPUTER",         0),
6213     ("COMMANDS",         0),
6214     ("EMEXIT",           0),
6215     ("PROBE",            OPTION_PROBE),
6216     ("SAVE",             0),
6217     ("FREEZE",           0),        # Synonym for SAVE
6218     ("ABANDON",          0),
6219     ("DESTRUCT",         0),
6220     ("DEATHRAY",         0),
6221     ("CAPTURE",          OPTION_CAPTURE),
6222     ("CLOAK",            OPTION_CLOAK),
6223     ("DEBUG",            0),
6224     ("MAYDAY",           0),
6225     ("SOS",              0),        # Synonym for MAYDAY
6226     ("CALL",             0),        # Synonym for MAYDAY
6227     ("QUIT",             0),
6228     ("HELP",             0),
6229     ("SCORE",            0),
6230     ("CURSES",            0),
6231     ("",                 0),
6232 ]
6233
6234 def listCommands():
6235     "Generate a list of legal commands."
6236     prout(_("LEGAL COMMANDS ARE:"))
6237     emitted = 0
6238     for (key, opt) in commands:
6239         if not opt or (opt & game.options):
6240             proutn("%-12s " % key)
6241             emitted += 1
6242             if emitted % 5 == 4:
6243                 skip(1)
6244     skip(1)
6245
6246 def helpme():
6247     "Browse on-line help."
6248     key = scanner.nexttok()
6249     while True:
6250         if key == "IHEOL":
6251             setwnd(prompt_window)
6252             proutn(_("Help on what command? "))
6253             key = scanner.nexttok()
6254         setwnd(message_window)
6255         if key == "IHEOL":
6256             return
6257         cmds = [x[0] for x in commands]
6258         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6259             break
6260         skip(1)
6261         listCommands()
6262         key = "IHEOL"
6263         scanner.chew()
6264         skip(1)
6265     cmd = scanner.token.upper()
6266     for directory in docpath:
6267         try:
6268             fp = open(os.path.join(directory, "sst.doc"), "r")
6269             break
6270         except IOError:
6271             pass
6272     else:
6273         prout(_("Spock-  \"Captain, that information is missing from the"))
6274         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6275         proutn(_("   in these directories: %s") % ":".join(docpath))
6276         prout(".\"")
6277         # This used to continue: "You need to find SST.DOC and put
6278         # it in the current directory."
6279         return
6280     while True:
6281         linebuf = fp.readline()
6282         if linebuf == '':
6283             prout(_("Spock- \"Captain, there is no information on that command.\""))
6284             fp.close()
6285             return
6286         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6287             linebuf = linebuf[3:].strip()
6288             if cmd.upper() == linebuf:
6289                 break
6290     skip(1)
6291     prout(_("Spock- \"Captain, I've found the following information:\""))
6292     skip(1)
6293     while True:
6294         linebuf = fp.readline()
6295         if "******" in linebuf:
6296             break
6297         proutn(linebuf)
6298     fp.close()
6299
6300 def makemoves():
6301     "Command-interpretation loop."
6302     def checkviol():
6303         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6304             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6305             game.ncviol += 1
6306             game.isviolreported = True
6307     while True:         # command loop
6308         drawmaps(1)
6309         while True:        # get a command
6310             hitme = False
6311             game.optime = game.justin = False
6312             scanner.chew()
6313             setwnd(prompt_window)
6314             clrscr()
6315             proutn("COMMAND> ")
6316             if scanner.nexttok() == "IHEOL":
6317                 if game.options & OPTION_CURSES:
6318                     makechart()
6319                 continue
6320             elif scanner.token == "":
6321                 continue
6322             game.ididit = False
6323             clrscr()
6324             setwnd(message_window)
6325             clrscr()
6326             abandon_passed = False
6327             cmd = ""    # Force cmd to persist after loop
6328             opt = 0     # Force opt to persist after loop
6329             for (cmd, opt) in commands:
6330                 # commands after ABANDON cannot be abbreviated
6331                 if cmd == "ABANDON":
6332                     abandon_passed = True
6333                 if cmd == scanner.token.upper() or (not abandon_passed \
6334                         and cmd.startswith(scanner.token.upper())):
6335                     break
6336             if cmd == "":
6337                 listCommands()
6338                 continue
6339             elif opt and not (opt & game.options):
6340                 huh()
6341             else:
6342                 break
6343         if game.options & OPTION_CURSES:
6344             prout("COMMAND> %s" % cmd)
6345         if cmd == "SRSCAN":                # srscan
6346             srscan()
6347         elif cmd == "STATUS":                # status
6348             status()
6349         elif cmd == "REQUEST":                # status request
6350             request()
6351         elif cmd == "LRSCAN":                # long range scan
6352             lrscan(silent=False)
6353         elif cmd == "PHASERS":                # phasers
6354             phasers()
6355             if game.ididit:
6356                 checkviol()
6357                 hitme = True
6358         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6359             torps()
6360             if game.ididit:
6361                 checkviol()
6362                 hitme = True
6363         elif cmd == "MOVE":                # move under warp
6364             warp(wcourse=None, involuntary=False)
6365         elif cmd == "SHIELDS":                # shields
6366             doshield(shraise=False)
6367             if game.ididit:
6368                 hitme = True
6369                 game.shldchg = False
6370         elif cmd == "DOCK":                # dock at starbase
6371             dock(True)
6372             if game.ididit:
6373                 attack(torps_ok=False)
6374         elif cmd == "DAMAGES":                # damage reports
6375             damagereport()
6376         elif cmd == "CHART":                # chart
6377             makechart()
6378         elif cmd == "IMPULSE":                # impulse
6379             impulse()
6380         elif cmd == "REST":                # rest
6381             wait()
6382             if game.ididit:
6383                 hitme = True
6384         elif cmd == "WARP":                # warp
6385             setwarp()
6386         elif cmd == "SENSORS":                # sensors
6387             sensor()
6388         elif cmd == "ORBIT":                # orbit
6389             orbit()
6390             if game.ididit:
6391                 hitme = True
6392         elif cmd == "TRANSPORT":                # transport "beam"
6393             beam()
6394         elif cmd == "MINE":                # mine
6395             mine()
6396             if game.ididit:
6397                 hitme = True
6398         elif cmd == "CRYSTALS":                # crystals
6399             usecrystals()
6400             if game.ididit:
6401                 hitme = True
6402         elif cmd == "SHUTTLE":                # shuttle
6403             shuttle()
6404             if game.ididit:
6405                 hitme = True
6406         elif cmd == "PLANETS":                # Planet list
6407             survey()
6408         elif cmd == "REPORT":                # Game Report
6409             report()
6410         elif cmd == "COMPUTER":                # use COMPUTER!
6411             eta()
6412         elif cmd == "COMMANDS":
6413             listCommands()
6414         elif cmd == "EMEXIT":                # Emergency exit
6415             clrscr()                        # Hide screen
6416             freeze(True)                # forced save
6417             raise SystemExit(1)                # And quick exit
6418         elif cmd == "PROBE":
6419             probe()                        # Launch probe
6420             if game.ididit:
6421                 hitme = True
6422         elif cmd == "ABANDON":                # Abandon Ship
6423             abandon()
6424         elif cmd == "DESTRUCT":                # Self Destruct
6425             selfdestruct()
6426         elif cmd == "SAVE":                # Save Game
6427             freeze(False)
6428             clrscr()
6429             if game.skill > SKILL_GOOD:
6430                 prout(_("WARNING--Saved games produce no plaques!"))
6431         elif cmd == "DEATHRAY":                # Try a desparation measure
6432             deathray()
6433             if game.ididit:
6434                 hitme = True
6435         elif cmd == "CAPTURE":
6436             capture()
6437         elif cmd == "CLOAK":
6438             cloak()
6439         elif cmd == "DEBUGCMD":                # What do we want for debug???
6440             debugme()
6441         elif cmd == "MAYDAY":                # Call for help
6442             mayday()
6443             if game.ididit:
6444                 hitme = True
6445         elif cmd == "QUIT":
6446             game.alldone = True                # quit the game
6447         elif cmd == "HELP":
6448             helpme()                        # get help
6449         elif cmd == "SCORE":
6450             score()                         # see current score
6451         elif cmd == "CURSES":
6452             game.options |= (OPTION_CURSES | OPTION_COLOR)
6453             iostart()
6454         while True:
6455             if game.alldone:
6456                 break                # Game has ended
6457             if game.optime != 0.0:
6458                 events()
6459                 if game.alldone:
6460                     break        # Events did us in
6461             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6462                 atover(False)
6463                 continue
6464             if hitme and not game.justin:
6465                 attack(torps_ok=True)
6466                 if game.alldone:
6467                     break
6468                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6469                     atover(False)
6470                     hitme = True
6471                     continue
6472             break
6473         if game.alldone:
6474             break
6475     if game.idebug:
6476         prout("=== Ending")
6477
6478 def cramen(ch):
6479     "Emit the name of an enemy or feature."
6480     if   ch == 'R': s = _("Romulan")
6481     elif ch == 'K': s = _("Klingon")
6482     elif ch == 'C': s = _("Commander")
6483     elif ch == 'S': s = _("Super-commander")
6484     elif ch == '*': s = _("Star")
6485     elif ch == 'P': s = _("Planet")
6486     elif ch == 'B': s = _("Starbase")
6487     elif ch == ' ': s = _("Black hole")
6488     elif ch == 'T': s = _("Tholian")
6489     elif ch == '#': s = _("Tholian web")
6490     elif ch == '?': s = _("Stranger")
6491     elif ch == '@': s = _("Inhabited World")
6492     else: s = "Unknown??"
6493     return s
6494
6495 def crmena(loud, enemy, loctype, w):
6496     "Emit the name of an enemy and his location."
6497     buf = ""
6498     if loud:
6499         buf += "***"
6500     buf += cramen(enemy) + _(" at ")
6501     if loctype == "quadrant":
6502         buf += _("Quadrant ")
6503     elif loctype == "sector":
6504         buf += _("Sector ")
6505     return buf + repr(w)
6506
6507 def crmshp():
6508     "Emit our ship name."
6509     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6510
6511 def stars():
6512     "Emit a line of stars"
6513     prouts("******************************************************")
6514     skip(1)
6515
6516 def expran(avrage):
6517     return -avrage*math.log(1e-7 + rnd.real())
6518
6519 def randplace(size):
6520     "Choose a random location."
6521     w = Coord()
6522     w.i = rnd.integer(size)
6523     w.j = rnd.integer(size)
6524     return w
6525
6526 class sstscanner:
6527     def __init__(self):
6528         self.type = None
6529         self.token = None
6530         self.real = 0.0
6531         self.inqueue = []
6532     def nexttok(self):
6533         # Get a token from the user
6534         self.real = 0.0
6535         self.token = ''
6536         # Fill the token quue if nothing here
6537         while not self.inqueue:
6538             sline = cgetline()
6539             if curwnd==prompt_window:
6540                 clrscr()
6541                 setwnd(message_window)
6542                 clrscr()
6543             if sline == '':
6544                 return None
6545             if not sline:
6546                 continue
6547             else:
6548                 self.inqueue = sline.lstrip().split() + ["\n"]
6549         # From here on in it's all looking at the queue
6550         self.token = self.inqueue.pop(0)
6551         if self.token == "\n":
6552             self.type = "IHEOL"
6553             return "IHEOL"
6554         try:
6555             self.real = float(self.token)
6556             self.type = "IHREAL"
6557             return "IHREAL"
6558         except ValueError:
6559             pass
6560         # Treat as alpha
6561         self.token = self.token.lower()
6562         self.type = "IHALPHA"
6563         self.real = None
6564         return "IHALPHA"
6565     def append(self, tok):
6566         self.inqueue.append(tok)
6567     def push(self, tok):
6568         self.inqueue.insert(0, tok)
6569     def waiting(self):
6570         return self.inqueue
6571     def chew(self):
6572         # Demand input for next scan
6573         self.inqueue = []
6574         self.real = self.token = None
6575     def sees(self, s):
6576         # compares s to item and returns true if it matches to the length of s
6577         return s.startswith(self.token)
6578     def int(self):
6579         # Round token value to nearest integer
6580         return int(round(self.real))
6581     def getcoord(self):
6582         s = Coord()
6583         self.nexttok()
6584         if self.type != "IHREAL":
6585             huh()
6586             return None
6587         s.i = self.int()-1
6588         self.nexttok()
6589         if self.type != "IHREAL":
6590             huh()
6591             return None
6592         s.j = self.int()-1
6593         return s
6594     def __repr__(self):
6595         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6596
6597 def ja():
6598     "Yes-or-no confirmation."
6599     scanner.chew()
6600     while True:
6601         scanner.nexttok()
6602         if scanner.token == 'y':
6603             return True
6604         if scanner.token == 'n':
6605             return False
6606         scanner.chew()
6607         proutn(_("Please answer with \"y\" or \"n\": "))
6608
6609 def huh():
6610     "Complain about unparseable input."
6611     scanner.chew()
6612     skip(1)
6613     prout(_("Beg your pardon, Captain?"))
6614
6615 def debugme():
6616     "Access to the internals for debugging."
6617     proutn("Reset levels? ")
6618     if ja():
6619         if game.energy < game.inenrg:
6620             game.energy = game.inenrg
6621         game.shield = game.inshld
6622         game.torps = game.intorps
6623         game.lsupres = game.inlsr
6624     proutn("Reset damage? ")
6625     if ja():
6626         for i in range(NDEVICES):
6627             if game.damage[i] > 0.0:
6628                 game.damage[i] = 0.0
6629     proutn("Toggle debug flag? ")
6630     if ja():
6631         game.idebug = not game.idebug
6632         if game.idebug:
6633             prout("Debug output ON")
6634         else:
6635             prout("Debug output OFF")
6636     proutn("Cause selective damage? ")
6637     if ja():
6638         for i in range(NDEVICES):
6639             proutn("Kill %s?" % device[i])
6640             scanner.chew()
6641             key = scanner.nexttok()
6642             if key == "IHALPHA" and scanner.sees("y"):
6643                 game.damage[i] = 10.0
6644     proutn("Examine/change events? ")
6645     if ja():
6646         ev = Event()
6647         w = Coord()
6648         legends = {
6649             FSNOVA:  "Supernova       ",
6650             FTBEAM:  "T Beam          ",
6651             FSNAP:   "Snapshot        ",
6652             FBATTAK: "Base Attack     ",
6653             FCDBAS:  "Base Destroy    ",
6654             FSCMOVE: "SC Move         ",
6655             FSCDBAS: "SC Base Destroy ",
6656             FDSPROB: "Probe Move      ",
6657             FDISTR:  "Distress Call   ",
6658             FENSLV:  "Enslavement     ",
6659             FREPRO:  "Klingon Build   ",
6660         }
6661         for i in range(1, NEVENTS):
6662             proutn(legends[i])
6663             if is_scheduled(i):
6664                 proutn("%.2f" % (scheduled(i)-game.state.date))
6665                 if i == FENSLV or i == FREPRO:
6666                     ev = findevent(i)
6667                     proutn(" in %s" % ev.quadrant)
6668             else:
6669                 proutn("never")
6670             proutn("? ")
6671             scanner.chew()
6672             key = scanner.nexttok()
6673             if key == 'n':
6674                 unschedule(i)
6675                 scanner.chew()
6676             elif key == "IHREAL":
6677                 ev = schedule(i, scanner.real)
6678                 if i == FENSLV or i == FREPRO:
6679                     scanner.chew()
6680                     proutn("In quadrant- ")
6681                     key = scanner.nexttok()
6682                     # "IHEOL" says to leave coordinates as they are
6683                     if key != "IHEOL":
6684                         if key != "IHREAL":
6685                             prout("Event %d canceled, no x coordinate." % (i))
6686                             unschedule(i)
6687                             continue
6688                         w.i = int(round(scanner.real))
6689                         key = scanner.nexttok()
6690                         if key != "IHREAL":
6691                             prout("Event %d canceled, no y coordinate." % (i))
6692                             unschedule(i)
6693                             continue
6694                         w.j = int(round(scanner.real))
6695                         ev.quadrant = w
6696         scanner.chew()
6697     proutn("Induce supernova here? ")
6698     if ja():
6699         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6700         atover(True)
6701
6702 if __name__ == '__main__':
6703     try:
6704         #global line, thing, game
6705         game = None
6706         thing = Thingy()
6707         game = Gamestate()
6708         rnd = randomizer()
6709         logfp = None
6710         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6711         if os.getenv("TERM"):
6712             game.options |= OPTION_CURSES
6713         else:
6714             game.options |= OPTION_TTY
6715         seed = int(time.time())
6716         (options, arguments) = getopt.getopt(sys.argv[1:], "cr:s:txV")
6717         replay = False
6718         for (switch, val) in options:
6719             if switch == '-r':
6720                 try:
6721                     replayfp = open(val, "r")
6722                 except IOError:
6723                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6724                     raise SystemExit(1)
6725                 try:
6726                     line = replayfp.readline().strip()
6727                     (leader, __, seed) = line.split()
6728                     seed = eval(seed)
6729                     line = replayfp.readline().strip()
6730                     arguments += line.split()[2:]
6731                     replay = True
6732                 except ValueError:
6733                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6734                     raise SystemExit(1)
6735                 game.options |= OPTION_TTY
6736                 game.options &=~ OPTION_CURSES
6737             elif switch == '-s':
6738                 seed = int(val)
6739             elif switch == '-t':
6740                 game.options |= OPTION_TTY
6741                 game.options &=~ OPTION_CURSES
6742             elif switch == '-x':
6743                 game.idebug = True
6744             elif switch == '-c':        # Enable curses debugging - undocumented
6745                 game.cdebug = True
6746             elif switch == '-V':
6747                 print("SST2K", version)
6748                 raise SystemExit(0)
6749             else:
6750                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6751                 raise SystemExit(1)
6752         # where to save the input in case of bugs
6753         if "TMPDIR" in os.environ:
6754             tmpdir = os.environ['TMPDIR']
6755         else:
6756             tmpdir = "/tmp"
6757         try:
6758             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6759         except IOError:
6760             sys.stderr.write("sst: warning, can't open logfile\n")
6761             sys.exit(1)
6762         if logfp:
6763             logfp.write("# seed %s\n" % seed)
6764             logfp.write("# options %s\n" % " ".join(arguments))
6765             logfp.write("# SST2K version %s\n" % version)
6766             logfp.write("# recorded by %s@%s on %s\n" % \
6767                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6768         rnd.seed(seed)
6769         scanner = sstscanner()
6770         for arg in arguments:
6771             scanner.append(arg)
6772         try:
6773             iostart()
6774             while True: # Play a game
6775                 setwnd(fullscreen_window)
6776                 clrscr()
6777                 prelim()
6778                 setup()
6779                 if game.alldone:
6780                     score()
6781                     game.alldone = False
6782                 else:
6783                     makemoves()
6784                 if replay:
6785                     break
6786                 skip(1)
6787                 stars()
6788                 skip(1)
6789                 if game.tourn and game.alldone:
6790                     proutn(_("Do you want your score recorded?"))
6791                     if ja():
6792                         scanner.chew()
6793                         scanner.push("\n")
6794                         freeze(False)
6795                 scanner.chew()
6796                 proutn(_("Do you want to play again? "))
6797                 if not ja():
6798                     break
6799             skip(1)
6800             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6801         finally:
6802             ioend()
6803         raise SystemExit(0)
6804     except KeyboardInterrupt:
6805         if logfp:
6806             logfp.close()
6807         print("")
6808
6809 # End.