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