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