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