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