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