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