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