Fix a small but fatal bug.
[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 damage(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 be 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)
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
3160 def score():
3161     "Compute player's score."
3162     timused = game.state.date - game.indate
3163     if (timused == 0 or (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) != 0) and timused < 5.0:
3164         timused = 5.0
3165     game.perdate = killrate()
3166     ithperd = 500*game.perdate + 0.5
3167     iwon = 0
3168     if game.gamewon:
3169         iwon = 100*game.skill
3170     if game.ship == 'E':
3171         klship = 0
3172     elif game.ship == 'F':
3173         klship = 1
3174     else:
3175         klship = 2
3176     game.score = 10*(game.inkling - game.state.remkl) \
3177              + 50*(game.incom - len(game.state.kcmdr)) \
3178              + ithperd + iwon \
3179              + 20*(game.inrom - game.state.nromrem) \
3180              + 200*(game.inscom - game.state.nscrem) \
3181                  - game.state.nromrem \
3182              + 3 * game.kcaptured \
3183              - badpoints()
3184     if not game.alive:
3185         game.score -= 200
3186     skip(2)
3187     prout(_("Your score --"))
3188     if game.inrom - game.state.nromrem:
3189         prout(_("%6d Romulans destroyed                 %5d") %
3190               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3191     if game.state.nromrem and game.gamewon:
3192         prout(_("%6d Romulans captured                  %5d") %
3193               (game.state.nromrem, game.state.nromrem))
3194     if game.inkling - game.state.remkl:
3195         prout(_("%6d ordinary Klingons destroyed        %5d") %
3196               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
3197     if game.incom - len(game.state.kcmdr):
3198         prout(_("%6d Klingon commanders destroyed       %5d") %
3199               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3200     if game.kcaptured:
3201         prout(_("%d Klingons captured                   %5d") %
3202               (game.kcaptured, 3 * game.kcaptured))
3203     if game.inscom - game.state.nscrem:
3204         prout(_("%6d Super-Commander destroyed          %5d") %
3205               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3206     if ithperd:
3207         prout(_("%6.2f Klingons per stardate              %5d") %
3208               (game.perdate, ithperd))
3209     if game.state.starkl:
3210         prout(_("%6d stars destroyed by your action     %5d") %
3211               (game.state.starkl, -5*game.state.starkl))
3212     if game.state.nplankl:
3213         prout(_("%6d planets destroyed by your action   %5d") %
3214               (game.state.nplankl, -10*game.state.nplankl))
3215     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3216         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3217               (game.state.nworldkl, -300*game.state.nworldkl))
3218     if game.state.basekl:
3219         prout(_("%6d bases destroyed by your action     %5d") %
3220               (game.state.basekl, -100*game.state.basekl))
3221     if game.nhelp:
3222         prout(_("%6d calls for help from starbase       %5d") %
3223               (game.nhelp, -45*game.nhelp))
3224     if game.casual:
3225         prout(_("%6d casualties incurred                %5d") %
3226               (game.casual, -game.casual))
3227     if game.abandoned:
3228         prout(_("%6d crew abandoned in space            %5d") %
3229               (game.abandoned, -3*game.abandoned))
3230     if klship:
3231         prout(_("%6d ship(s) lost or destroyed          %5d") %
3232               (klship, -100*klship))
3233     if game.ncviol > 0:
3234         if ncviol == 1:
3235             prout(_("1 Treaty of Algeron violation          -100"))
3236         else:
3237             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3238                   (ncviol, -100*ncviol))
3239     if not game.alive:
3240         prout(_("Penalty for getting yourself killed        -200"))
3241     if game.gamewon:
3242         proutn(_("Bonus for winning "))
3243         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3244         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3245         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3246         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3247         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3248         prout("           %5d" % iwon)
3249     skip(1)
3250     prout(_("TOTAL SCORE                               %5d") % game.score)
3251
3252 def plaque():
3253     "Emit winner's commemmorative plaque."
3254     skip(2)
3255     while True:
3256         proutn(_("File or device name for your plaque: "))
3257         winner = cgetline()
3258         try:
3259             fp = open(winner, "w")
3260             break
3261         except IOError:
3262             prout(_("Invalid name."))
3263
3264     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3265     winner = cgetline()
3266     # The 38 below must be 64 for 132-column paper
3267     nskip = 38 - len(winner)/2
3268     fp.write("\n\n\n\n")
3269     # --------DRAW ENTERPRISE PICTURE.
3270     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3271     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3272     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3273     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3274     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3275     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3276     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3277     fp.write("                                      EEE           E          E            E  E\n")
3278     fp.write("                                                       E         E          E  E\n")
3279     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3280     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3281     fp.write("                                                    :E    :                 EEEE       E\n")
3282     fp.write("                                                   .-E   -:-----                       E\n")
3283     fp.write("                                                    :E    :                            E\n")
3284     fp.write("                                                      EE  :                    EEEEEEEE\n")
3285     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3286     fp.write("\n\n\n")
3287     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3288     fp.write("\n\n\n\n")
3289     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3290     fp.write("\n")
3291     fp.write(_("                                                Starfleet Command bestows to you\n"))
3292     fp.write("\n")
3293     fp.write("%*s%s\n\n" % (nskip, "", winner))
3294     fp.write(_("                                                           the rank of\n\n"))
3295     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3296     fp.write("                                                          ")
3297     if game.skill ==  SKILL_EXPERT:
3298         fp.write(_(" Expert level\n\n"))
3299     elif game.skill == SKILL_EMERITUS:
3300         fp.write(_("Emeritus level\n\n"))
3301     else:
3302         fp.write(_(" Cheat level\n\n"))
3303     timestring = time.ctime()
3304     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3305              (timestring+4, timestring+20, timestring+11))
3306     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3307     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3308     fp.close()
3309
3310 # Code from io.c begins here
3311
3312 rows = linecount = 0        # for paging
3313 stdscr = None
3314 replayfp = None
3315 fullscreen_window = None
3316 srscan_window     = None   # Short range scan
3317 report_window     = None   # Report legends for status window
3318 status_window     = None   # The status window itself
3319 lrscan_window     = None   # Long range scan
3320 message_window    = None   # Main window for scrolling text
3321 prompt_window     = None   # Prompt window at bottom of display
3322 curwnd = None
3323
3324 def iostart():
3325     global stdscr, rows
3326     # for some recent versions of python2, the following enables UTF8
3327     # for the older ones we probably need to set C locale, and python3
3328     # has no problems at all
3329     if sys.version_info[0] < 3:
3330         locale.setlocale(locale.LC_ALL, "")
3331     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3332     gettext.textdomain("sst")
3333     if not (game.options & OPTION_CURSES):
3334         ln_env = os.getenv("LINES")
3335         if ln_env:
3336             rows = ln_env
3337         else:
3338             rows = 25
3339     else:
3340         stdscr = curses.initscr()
3341         stdscr.keypad(True)
3342         curses.nonl()
3343         curses.cbreak()
3344         if game.options & OPTION_COLOR:
3345             curses.start_color()
3346             curses.use_default_colors()
3347             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3348             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3349             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3350             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3351             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3352             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3353             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3354             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3355         global fullscreen_window, srscan_window, report_window, status_window
3356         global lrscan_window, message_window, prompt_window
3357         (rows, _columns)   = stdscr.getmaxyx()
3358         fullscreen_window = stdscr
3359         srscan_window     = curses.newwin(12, 25, 0,       0)
3360         report_window     = curses.newwin(11, 0,  1,       25)
3361         status_window     = curses.newwin(10, 0,  1,       39)
3362         lrscan_window     = curses.newwin(5,  0,  0,       64)
3363         message_window    = curses.newwin(0,  0,  12,      0)
3364         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3365         message_window.scrollok(True)
3366         setwnd(fullscreen_window)
3367
3368 def ioend():
3369     "Wrap up I/O."
3370     if game.options & OPTION_CURSES:
3371         stdscr.keypad(False)
3372         curses.echo()
3373         curses.nocbreak()
3374         curses.endwin()
3375
3376 def waitfor():
3377     "Wait for user action -- OK to do nothing if on a TTY"
3378     if game.options & OPTION_CURSES:
3379         stdscr.getch()
3380
3381 def announce():
3382     skip(1)
3383     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3384     skip(1)
3385
3386 def pause_game():
3387     if game.skill > SKILL_FAIR:
3388         prompt = _("[CONTINUE?]")
3389     else:
3390         prompt = _("[PRESS ENTER TO CONTINUE]")
3391
3392     if game.options & OPTION_CURSES:
3393         drawmaps(0)
3394         setwnd(prompt_window)
3395         prompt_window.clear()
3396         prompt_window.addstr(prompt)
3397         prompt_window.getstr()
3398         prompt_window.clear()
3399         prompt_window.refresh()
3400         setwnd(message_window)
3401     else:
3402         global linecount
3403         sys.stdout.write('\n')
3404         proutn(prompt)
3405         if not replayfp:
3406             input()
3407         sys.stdout.write('\n' * rows)
3408         linecount = 0
3409
3410 def skip(i):
3411     "Skip i lines.  Pause game if this would cause a scrolling event."
3412     for _dummy in range(i):
3413         if game.options & OPTION_CURSES:
3414             (y, _x) = curwnd.getyx()
3415             try:
3416                 curwnd.move(y+1, 0)
3417             except curses.error:
3418                 pass
3419         else:
3420             global linecount
3421             linecount += 1
3422             if rows and linecount >= rows:
3423                 pause_game()
3424             else:
3425                 sys.stdout.write('\n')
3426
3427 def proutn(proutntline):
3428     "Utter a line with no following line feed."
3429     if game.options & OPTION_CURSES:
3430         (y, x) = curwnd.getyx()
3431         (my, _mx) = curwnd.getmaxyx()
3432         if curwnd == message_window and y >= my - 2:
3433             pause_game()
3434             clrscr()
3435         # Uncomment this to debug curses problems
3436         if logfp:
3437             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3438         curwnd.addstr(proutntline)
3439         curwnd.refresh()
3440     else:
3441         sys.stdout.write(proutntline)
3442         sys.stdout.flush()
3443
3444 def prout(proutline):
3445     proutn(proutline)
3446     skip(1)
3447
3448 def prouts(proutsline):
3449     "Emit slowly!"
3450     for c in proutsline:
3451         if not replayfp or replayfp.closed:        # Don't slow down replays
3452             time.sleep(0.03)
3453         proutn(c)
3454         if game.options & OPTION_CURSES:
3455             curwnd.refresh()
3456         else:
3457             sys.stdout.flush()
3458     if not replayfp or replayfp.closed:
3459         time.sleep(0.03)
3460
3461 def cgetline():
3462     "Get a line of input."
3463     if game.options & OPTION_CURSES:
3464         linein = curwnd.getstr() + "\n"
3465         curwnd.refresh()
3466     else:
3467         if replayfp and not replayfp.closed:
3468             while True:
3469                 linein = replayfp.readline()
3470                 proutn(linein)
3471                 if linein == '':
3472                     prout("*** Replay finished")
3473                     replayfp.close()
3474                     break
3475                 elif linein[0] != "#":
3476                     break
3477         else:
3478             linein = eval(input()) + "\n"
3479     if logfp:
3480         logfp.write(linein)
3481     return linein
3482
3483 def setwnd(wnd):
3484     "Change windows -- OK for this to be a no-op in tty mode."
3485     global curwnd
3486     if game.options & OPTION_CURSES:
3487         # Uncomment this to debug curses problems
3488         if logfp:
3489             if wnd == fullscreen_window:
3490                 legend = "fullscreen"
3491             elif wnd == srscan_window:
3492                 legend = "srscan"
3493             elif wnd == report_window:
3494                 legend = "report"
3495             elif wnd == status_window:
3496                 legend = "status"
3497             elif wnd == lrscan_window:
3498                 legend = "lrscan"
3499             elif wnd == message_window:
3500                 legend = "message"
3501             elif wnd == prompt_window:
3502                 legend = "prompt"
3503             else:
3504                 legend = "unknown"
3505             logfp.write("#curses: setwnd(%s)\n" % legend)
3506         curwnd = wnd
3507         # Some curses implementations get confused when you try this.
3508         try:
3509             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3510         except curses.error:
3511             pass
3512
3513 def clreol():
3514     "Clear to end of line -- can be a no-op in tty mode"
3515     if game.options & OPTION_CURSES:
3516         curwnd.clrtoeol()
3517         curwnd.refresh()
3518
3519 def clrscr():
3520     "Clear screen -- can be a no-op in tty mode."
3521     global linecount
3522     if game.options & OPTION_CURSES:
3523         curwnd.clear()
3524         curwnd.move(0, 0)
3525         curwnd.refresh()
3526     linecount = 0
3527
3528 def textcolor(color=DEFAULT):
3529     if game.options & OPTION_COLOR:
3530         if color == DEFAULT:
3531             curwnd.attrset(0)
3532         elif color ==  BLACK:
3533             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3534         elif color ==  BLUE:
3535             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3536         elif color ==  GREEN:
3537             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3538         elif color ==  CYAN:
3539             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3540         elif color ==  RED:
3541             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3542         elif color ==  MAGENTA:
3543             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3544         elif color ==  BROWN:
3545             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3546         elif color ==  LIGHTGRAY:
3547             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3548         elif color ==  DARKGRAY:
3549             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3550         elif color ==  LIGHTBLUE:
3551             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3552         elif color ==  LIGHTGREEN:
3553             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3554         elif color ==  LIGHTCYAN:
3555             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3556         elif color ==  LIGHTRED:
3557             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3558         elif color ==  LIGHTMAGENTA:
3559             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3560         elif color ==  YELLOW:
3561             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3562         elif color ==  WHITE:
3563             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3564
3565 def highvideo():
3566     if game.options & OPTION_COLOR:
3567         curwnd.attron(curses.A_REVERSE)
3568
3569 #
3570 # Things past this point have policy implications.
3571 #
3572
3573 def drawmaps(mode):
3574     "Hook to be called after moving to redraw maps."
3575     if game.options & OPTION_CURSES:
3576         if mode == 1:
3577             sensor()
3578         setwnd(srscan_window)
3579         curwnd.move(0, 0)
3580         srscan()
3581         if mode != 2:
3582             setwnd(status_window)
3583             status_window.clear()
3584             status_window.move(0, 0)
3585             setwnd(report_window)
3586             report_window.clear()
3587             report_window.move(0, 0)
3588             status()
3589             setwnd(lrscan_window)
3590             lrscan_window.clear()
3591             lrscan_window.move(0, 0)
3592             lrscan(silent=False)
3593
3594 def put_srscan_sym(w, sym):
3595     "Emit symbol for short-range scan."
3596     srscan_window.move(w.i+1, w.j*2+2)
3597     srscan_window.addch(sym)
3598     srscan_window.refresh()
3599
3600 def boom(w):
3601     "Enemy fall down, go boom."
3602     if game.options & OPTION_CURSES:
3603         drawmaps(2)
3604         setwnd(srscan_window)
3605         srscan_window.attron(curses.A_REVERSE)
3606         put_srscan_sym(w, game.quad[w.i][w.j])
3607         #sound(500)
3608         #time.sleep(1.0)
3609         #nosound()
3610         srscan_window.attroff(curses.A_REVERSE)
3611         put_srscan_sym(w, game.quad[w.i][w.j])
3612         curses.delay_output(500)
3613         setwnd(message_window)
3614
3615 def warble():
3616     "Sound and visual effects for teleportation."
3617     if game.options & OPTION_CURSES:
3618         drawmaps(2)
3619         setwnd(message_window)
3620         #sound(50)
3621     prouts("     . . . . .     ")
3622     if game.options & OPTION_CURSES:
3623         #curses.delay_output(1000)
3624         #nosound()
3625         pass
3626
3627 def tracktorpedo(w, step, i, n, iquad):
3628     "Torpedo-track animation."
3629     if not game.options & OPTION_CURSES:
3630         if step == 1:
3631             if n != 1:
3632                 skip(1)
3633                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3634             else:
3635                 skip(1)
3636                 proutn(_("Torpedo track- "))
3637         elif step==4 or step==9:
3638             skip(1)
3639         proutn("%s   " % w)
3640     else:
3641         if not damaged(DSRSENS) or game.condition=="docked":
3642             if i != 0 and step == 1:
3643                 drawmaps(2)
3644                 time.sleep(0.4)
3645             if (iquad=='.') or (iquad==' '):
3646                 put_srscan_sym(w, '+')
3647                 #sound(step*10)
3648                 #time.sleep(0.1)
3649                 #nosound()
3650                 put_srscan_sym(w, iquad)
3651             else:
3652                 curwnd.attron(curses.A_REVERSE)
3653                 put_srscan_sym(w, iquad)
3654                 #sound(500)
3655                 #time.sleep(1.0)
3656                 #nosound()
3657                 curwnd.attroff(curses.A_REVERSE)
3658                 put_srscan_sym(w, iquad)
3659         else:
3660             proutn("%s   " % w)
3661
3662 def makechart():
3663     "Display the current galaxy chart."
3664     if game.options & OPTION_CURSES:
3665         setwnd(message_window)
3666         message_window.clear()
3667     chart()
3668     if game.options & OPTION_TTY:
3669         skip(1)
3670
3671 NSYM        = 14
3672
3673 def prstat(txt, data):
3674     proutn(txt)
3675     if game.options & OPTION_CURSES:
3676         skip(1)
3677         setwnd(status_window)
3678     else:
3679         proutn(" " * (NSYM - len(txt)))
3680     proutn(data)
3681     skip(1)
3682     if game.options & OPTION_CURSES:
3683         setwnd(report_window)
3684
3685 # Code from moving.c begins here
3686
3687 def imove(icourse=None, noattack=False):
3688     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3689     w = Coord()
3690
3691     def newquadrant(noattack):
3692         # Leaving quadrant -- allow final enemy attack
3693         # Don't set up attack if being pushed by nova or cloaked
3694         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3695             newcnd()
3696             for enemy in game.enemies:
3697                 finald = (w - enemy.location).distance()
3698                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3699             # Stas Sergeev added the condition
3700             # that attacks only happen if Klingons
3701             # are present and your skill is good.
3702             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3703                 attack(torps_ok=False)
3704             if game.alldone:
3705                 return
3706         # check for edge of galaxy
3707         kinks = 0
3708         while True:
3709
3710
3711             kink = False
3712             if icourse.final.i < 0:
3713                 icourse.final.i = -icourse.final.i
3714                 kink = True
3715             if icourse.final.j < 0:
3716                 icourse.final.j = -icourse.final.j
3717                 kink = True
3718             if icourse.final.i >= GALSIZE*QUADSIZE:
3719                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3720                 kink = True
3721             if icourse.final.j >= GALSIZE*QUADSIZE:
3722                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3723                 kink = True
3724             if kink:
3725                 kinks += 1
3726             else:
3727                 break
3728         if kinks:
3729             game.nkinks += 1
3730             if game.nkinks == 3:
3731                 # Three strikes -- you're out!
3732                 finish(FNEG3)
3733                 return
3734             skip(1)
3735             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3736             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3737             prout(_("YOU WILL BE DESTROYED."))
3738         # Compute final position in new quadrant
3739         if trbeam: # Don't bother if we are to be beamed
3740             return
3741         game.quadrant = icourse.final.quadrant()
3742         game.sector = icourse.final.sector()
3743         skip(1)
3744         prout(_("Entering Quadrant %s.") % game.quadrant)
3745         game.quad[game.sector.i][game.sector.j] = game.ship
3746         newqad()
3747         if game.skill>SKILL_NOVICE:
3748             attack(torps_ok=False)
3749
3750     def check_collision(h):
3751         iquad = game.quad[h.i][h.j]
3752         if iquad != '.':
3753             # object encountered in flight path
3754             stopegy = 50.0*icourse.distance/game.optime
3755             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3756                 for enemy in game.enemies:
3757                     if enemy.location == game.sector:
3758                         collision(rammed=False, enemy=enemy)
3759                         return True
3760                 # This should not happen
3761                 prout(_("Which way did he go?"))
3762                 return False
3763             elif iquad == ' ':
3764                 skip(1)
3765                 prouts(_("***RED ALERT!  RED ALERT!"))
3766                 skip(1)
3767                 proutn("***" + crmshp())
3768                 proutn(_(" pulled into black hole at Sector %s") % h)
3769                 # Getting pulled into a black hole was certain
3770                 # death in Almy's original.  Stas Sergeev added a
3771                 # possibility that you'll get timewarped instead.
3772                 n=0
3773                 for m in range(NDEVICES):
3774                     if game.damage[m]>0:
3775                         n += 1
3776                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3777                 if (game.options & OPTION_BLKHOLE) and withprob(1-probf):
3778                     timwrp()
3779                 else:
3780                     finish(FHOLE)
3781                 return True
3782             else:
3783                 # something else
3784                 skip(1)
3785                 proutn(crmshp())
3786                 if iquad == '#':
3787                     prout(_(" encounters Tholian web at %s;") % h)
3788                 else:
3789                     prout(_(" blocked by object at %s;") % h)
3790                 proutn(_("Emergency stop required "))
3791                 prout(_("%2d units of energy.") % int(stopegy))
3792                 game.energy -= stopegy
3793                 if game.energy <= 0:
3794                     finish(FNRG)
3795                 return True
3796         return False
3797
3798     trbeam = False
3799     if game.inorbit:
3800         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3801         game.inorbit = False
3802     # If tractor beam is to occur, don't move full distance
3803     if game.state.date+game.optime >= scheduled(FTBEAM):
3804         if game.iscloaked:
3805             # We can't be tractor beamed if cloaked,
3806             # so move the event into the future
3807             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
3808             pass
3809         else:
3810             trbeam = True
3811             game.condition = "red"
3812             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3813             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3814     # Move out
3815     game.quad[game.sector.i][game.sector.j] = '.'
3816     for _m in range(icourse.moves):
3817         icourse.nexttok()
3818         w = icourse.sector()
3819         if icourse.origin.quadrant() != icourse.location.quadrant():
3820             newquadrant(noattack)
3821             break
3822         elif check_collision(w):
3823             print("Collision detected")
3824             break
3825         else:
3826             game.sector = w
3827     # We're in destination quadrant -- compute new average enemy distances
3828     game.quad[game.sector.i][game.sector.j] = game.ship
3829     if game.enemies:
3830         for enemy in game.enemies:
3831             finald = (w-enemy.location).distance()
3832             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3833             enemy.kdist = finald
3834         sortenemies()
3835         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3836             attack(torps_ok=False)
3837         for enemy in game.enemies:
3838             enemy.kavgd = enemy.kdist
3839     newcnd()
3840     drawmaps(0)
3841     setwnd(message_window)
3842     return
3843
3844 def dock(verbose):
3845     "Dock our ship at a starbase."
3846     scanner.chew()
3847     if game.condition == "docked" and verbose:
3848         prout(_("Already docked."))
3849         return
3850     if game.inorbit:
3851         prout(_("You must first leave standard orbit."))
3852         return
3853     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3854         prout(crmshp() + _(" not adjacent to base."))
3855         return
3856     if game.iscloaked:
3857         prout(_("You cannot dock while cloaked."))
3858         return
3859     game.condition = "docked"
3860     if verbose:
3861         prout(_("Docked."))
3862     game.ididit = True
3863     if game.energy < game.inenrg:
3864         game.energy = game.inenrg
3865     game.shield = game.inshld
3866     game.torps = game.intorps
3867     game.lsupres = game.inlsr
3868     game.state.crew = FULLCREW
3869     if game.brigcapacity-game.brigfree > 0:
3870         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3871         game.kcaptured += game.brigcapacity-game.brigfree
3872         game.brigfree = game.brigcapacity
3873     if not damaged(DRADIO) and \
3874         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3875         # get attack report from base
3876         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3877         attackreport(False)
3878         game.iseenit = True
3879
3880 def cartesian(loc1=None, loc2=None):
3881     if loc1 is None:
3882         return game.quadrant * QUADSIZE + game.sector
3883     elif loc2 is None:
3884         return game.quadrant * QUADSIZE + loc1
3885     else:
3886         return loc1 * QUADSIZE + loc2
3887
3888 def getcourse(isprobe):
3889     "Get a course and distance from the user."
3890     key = ""
3891     dquad = copy.copy(game.quadrant)
3892     navmode = "unspecified"
3893     itemp = "curt"
3894     dsect = Coord()
3895     iprompt = False
3896     if game.landed and not isprobe:
3897         prout(_("Dummy! You can't leave standard orbit until you"))
3898         proutn(_("are back aboard the ship."))
3899         scanner.chew()
3900         raise TrekError
3901     while navmode == "unspecified":
3902         if damaged(DNAVSYS):
3903             if isprobe:
3904                 prout(_("Computer damaged; manual navigation only"))
3905             else:
3906                 prout(_("Computer damaged; manual movement only"))
3907             scanner.chew()
3908             navmode = "manual"
3909             key = "IHEOL"
3910             break
3911         key = scanner.nexttok()
3912         if key == "IHEOL":
3913             proutn(_("Manual or automatic- "))
3914             iprompt = True
3915             scanner.chew()
3916         elif key == "IHALPHA":
3917             if scanner.sees("manual"):
3918                 navmode = "manual"
3919                 key = scanner.nexttok()
3920                 break
3921             elif scanner.sees("automatic"):
3922                 navmode = "automatic"
3923                 key = scanner.nexttok()
3924                 break
3925             else:
3926                 huh()
3927                 scanner.chew()
3928                 raise TrekError
3929         else: # numeric
3930             if isprobe:
3931                 prout(_("(Manual navigation assumed.)"))
3932             else:
3933                 prout(_("(Manual movement assumed.)"))
3934             navmode = "manual"
3935             break
3936     delta = Coord()
3937     if navmode == "automatic":
3938         while key == "IHEOL":
3939             if isprobe:
3940                 proutn(_("Target quadrant or quadrant&sector- "))
3941             else:
3942                 proutn(_("Destination sector or quadrant&sector- "))
3943             scanner.chew()
3944             iprompt = True
3945             key = scanner.nexttok()
3946         if key != "IHREAL":
3947             huh()
3948             raise TrekError
3949         xi = int(round(scanner.real))-1
3950         key = scanner.nexttok()
3951         if key != "IHREAL":
3952             huh()
3953             raise TrekError
3954         xj = int(round(scanner.real))-1
3955         key = scanner.nexttok()
3956         if key == "IHREAL":
3957             # both quadrant and sector specified
3958             xk = int(round(scanner.real))-1
3959             key = scanner.nexttok()
3960             if key != "IHREAL":
3961                 huh()
3962                 raise TrekError
3963             xl = int(round(scanner.real))-1
3964             dquad.i = xi
3965             dquad.j = xj
3966             dsect.i = xk
3967             dsect.j = xl
3968         else:
3969             # only one pair of numbers was specified
3970             if isprobe:
3971                 # only quadrant specified -- go to center of dest quad
3972                 dquad.i = xi
3973                 dquad.j = xj
3974                 dsect.j = dsect.i = 4        # preserves 1-origin behavior
3975             else:
3976                 # only sector specified
3977                 dsect.i = xi
3978                 dsect.j = xj
3979             itemp = "normal"
3980         if not dquad.valid_quadrant() or not dsect.valid_sector():
3981             huh()
3982             raise TrekError
3983         skip(1)
3984         if not isprobe:
3985             if itemp > "curt":
3986                 if iprompt:
3987                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
3988             else:
3989                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
3990         # the actual deltas get computed here
3991         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
3992         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
3993     else: # manual
3994         while key == "IHEOL":
3995             proutn(_("X and Y displacements- "))
3996             scanner.chew()
3997             iprompt = True
3998             key = scanner.nexttok()
3999         itemp = "verbose"
4000         if key != "IHREAL":
4001             huh()
4002             raise TrekError
4003         delta.j = scanner.real
4004         key = scanner.nexttok()
4005         if key != "IHREAL":
4006             huh()
4007             raise TrekError
4008         delta.i = scanner.real
4009     # Check for zero movement
4010     if delta.i == 0 and delta.j == 0:
4011         scanner.chew()
4012         raise TrekError
4013     if itemp == "verbose" and not isprobe:
4014         skip(1)
4015         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4016     scanner.chew()
4017     return course(bearing=delta.bearing(), distance=delta.distance())
4018
4019 class course:
4020     def __init__(self, bearing, distance, origin=None):
4021         self.distance = distance
4022         self.bearing = bearing
4023         if origin is None:
4024             self.origin = cartesian(game.quadrant, game.sector)
4025         else:
4026             self.origin = origin
4027         # The bearing() code we inherited from FORTRAN is actually computing
4028         # clockface directions!
4029         if self.bearing < 0.0:
4030             self.bearing += 12.0
4031         self.angle = ((15.0 - self.bearing) * 0.5235988)
4032         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4033         bigger = max(abs(self.increment.i), abs(self.increment.j))
4034         self.increment /= bigger
4035         self.moves = int(round(10*self.distance*bigger))
4036         self.reset()
4037         self.final = (self.location + self.moves*self.increment).roundtogrid()
4038         self.location = self.origin
4039         self.nextlocation = None
4040     def reset(self):
4041         self.location = self.origin
4042         self.step = 0
4043     def arrived(self):
4044         return self.location.roundtogrid() == self.final
4045     def nexttok(self):
4046         "Next step on course."
4047         self.step += 1
4048         self.nextlocation = self.location + self.increment
4049         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4050         self.location = self.nextlocation
4051         return samequad
4052     def quadrant(self):
4053         return self.location.quadrant()
4054     def sector(self):
4055         return self.location.sector()
4056     def power(self, w):
4057         return self.distance*(w**3)*(game.shldup+1)
4058     def time(self, w):
4059         return 10.0*self.distance/w**2
4060
4061 def impulse():
4062     "Move under impulse power."
4063     game.ididit = False
4064     if damaged(DIMPULS):
4065         scanner.chew()
4066         skip(1)
4067         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4068         return
4069     if game.energy > 30.0:
4070         try:
4071             icourse = getcourse(isprobe=False)
4072         except TrekError:
4073             return
4074         power = 20.0 + 100.0*icourse.distance
4075     else:
4076         power = 30.0
4077     if power >= game.energy:
4078         # Insufficient power for trip
4079         skip(1)
4080         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4081         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4082         if game.energy > 30:
4083             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4084                    int(0.01 * (game.energy-20.0)-0.05))
4085             prout(_(" quadrants.\""))
4086         else:
4087             prout(_("quadrant.  They are, therefore, useless.\""))
4088         scanner.chew()
4089         return
4090     # Make sure enough time is left for the trip
4091     game.optime = icourse.distance/0.095
4092     if game.optime >= game.state.remtime:
4093         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4094         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4095         proutn(_("we dare spend the time?\" "))
4096         if not ja():
4097             return
4098     # Activate impulse engines and pay the cost
4099     imove(icourse, noattack=False)
4100     game.ididit = True
4101     if game.alldone:
4102         return
4103     power = 20.0 + 100.0*icourse.distance
4104     game.energy -= power
4105     game.optime = icourse.distance/0.095
4106     if game.energy <= 0:
4107         finish(FNRG)
4108     return
4109
4110 def warp(wcourse, involuntary):
4111     "ove under warp drive."
4112     blooey = False; twarp = False
4113     if not involuntary: # Not WARPX entry
4114         game.ididit = False
4115         if game.iscloaked:
4116             scanner.chew()
4117             skip(1)
4118             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4119             return
4120         if game.damage[DWARPEN] > 10.0:
4121             scanner.chew()
4122             skip(1)
4123             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4124             return
4125         if damaged(DWARPEN) and game.warpfac > 4.0:
4126             scanner.chew()
4127             skip(1)
4128             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4129             prout(_("  is repaired, I can only give you warp 4.\""))
4130             return
4131                # Read in course and distance
4132         if wcourse is None:
4133             try:
4134                 wcourse = getcourse(isprobe=False)
4135             except TrekError:
4136                 return
4137         # Make sure starship has enough energy for the trip
4138         # Note: this formula is slightly different from the C version,
4139         # and lets you skate a bit closer to the edge.
4140         if wcourse.power(game.warpfac) >= game.energy:
4141             # Insufficient power for trip
4142             game.ididit = False
4143             skip(1)
4144             prout(_("Engineering to bridge--"))
4145             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4146                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4147                 if iwarp <= 0:
4148                     prout(_("We can't do it, Captain. We don't have enough energy."))
4149                 else:
4150                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4151                     if game.shldup:
4152                         prout(",")
4153                         prout(_("if you'll lower the shields."))
4154                     else:
4155                         prout(".")
4156             else:
4157                 prout(_("We haven't the energy to go that far with the shields up."))
4158             return
4159         # Make sure enough time is left for the trip
4160         game.optime = wcourse.time(game.warpfac)
4161         if game.optime >= 0.8*game.state.remtime:
4162             skip(1)
4163             prout(_("First Officer Spock- \"Captain, I compute that such"))
4164             proutn(_("  a trip would require approximately %2.0f") %
4165                    (100.0*game.optime/game.state.remtime))
4166             prout(_(" percent of our"))
4167             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4168             if not ja():
4169                 game.ididit = False
4170                 game.optime=0
4171                 return
4172     # Entry WARPX
4173     if game.warpfac > 6.0:
4174         # Decide if engine damage will occur
4175         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4176         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4177         if prob > randreal():
4178             blooey = True
4179             wcourse.distance = randreal(wcourse.distance)
4180         # Decide if time warp will occur
4181         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
4182             twarp = True
4183         if game.idebug and game.warpfac==10 and not twarp:
4184             blooey = False
4185             proutn("=== Force time warp? ")
4186             if ja():
4187                 twarp = True
4188         if blooey or twarp:
4189             # If time warp or engine damage, check path
4190             # If it is obstructed, don't do warp or damage
4191             look = wcourse.moves
4192             while look > 0:
4193                 look -= 1
4194                 wcourse.nexttok()
4195                 w = wcourse.sector()
4196                 if not w.valid_sector():
4197                     break
4198                 if game.quad[w.i][w.j] != '.':
4199                     blooey = False
4200                     twarp = False
4201             wcourse.reset()
4202     # Activate Warp Engines and pay the cost
4203     imove(wcourse, noattack=False)
4204     if game.alldone:
4205         return
4206     game.energy -= wcourse.power(game.warpfac)
4207     if game.energy <= 0:
4208         finish(FNRG)
4209     game.optime = wcourse.time(game.warpfac)
4210     if twarp:
4211         timwrp()
4212     if blooey:
4213         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
4214         skip(1)
4215         prout(_("Engineering to bridge--"))
4216         prout(_("  Scott here.  The warp engines are damaged."))
4217         prout(_("  We'll have to reduce speed to warp 4."))
4218     game.ididit = True
4219     return
4220
4221 def setwarp():
4222     "Change the warp factor."
4223     while True:
4224         key=scanner.nexttok()
4225         if key != "IHEOL":
4226             break
4227         scanner.chew()
4228         proutn(_("Warp factor- "))
4229     if key != "IHREAL":
4230         huh()
4231         return
4232     if game.damage[DWARPEN] > 10.0:
4233         prout(_("Warp engines inoperative."))
4234         return
4235     if damaged(DWARPEN) and scanner.real > 4.0:
4236         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4237         prout(_("  but right now we can only go warp 4.\""))
4238         return
4239     if scanner.real > 10.0:
4240         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4241         return
4242     if scanner.real < 1.0:
4243         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4244         return
4245     oldfac = game.warpfac
4246     game.warpfac = scanner.real
4247     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4248         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4249               int(game.warpfac))
4250         return
4251     if game.warpfac < 8.00:
4252         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4253         return
4254     if game.warpfac == 10.0:
4255         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4256         return
4257     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4258     return
4259
4260 def atover(igrab):
4261     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4262     scanner.chew()
4263     # is captain on planet?
4264     if game.landed:
4265         if damaged(DTRANSP):
4266             finish(FPNOVA)
4267             return
4268         prout(_("Scotty rushes to the transporter controls."))
4269         if game.shldup:
4270             prout(_("But with the shields up it's hopeless."))
4271             finish(FPNOVA)
4272         prouts(_("His desperate attempt to rescue you . . ."))
4273         if withprob(0.5):
4274             prout(_("fails."))
4275             finish(FPNOVA)
4276             return
4277         prout(_("SUCCEEDS!"))
4278         if game.imine:
4279             game.imine = False
4280             proutn(_("The crystals mined were "))
4281             if withprob(0.25):
4282                 prout(_("lost."))
4283             else:
4284                 prout(_("saved."))
4285                 game.icrystl = True
4286     if igrab:
4287         return
4288     # Check to see if captain in shuttle craft
4289     if game.icraft:
4290         finish(FSTRACTOR)
4291     if game.alldone:
4292         return
4293     # Inform captain of attempt to reach safety
4294     skip(1)
4295     while True:
4296         if game.justin:
4297             prouts(_("***RED ALERT!  RED ALERT!"))
4298             skip(1)
4299             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4300             prouts(_("   a supernova."))
4301             skip(2)
4302         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4303         prout(_("safely out of quadrant."))
4304         if not damaged(DRADIO):
4305             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4306         # Try to use warp engines
4307         if damaged(DWARPEN):
4308             skip(1)
4309             prout(_("Warp engines damaged."))
4310             finish(FSNOVAED)
4311             return
4312         game.warpfac = randreal(6.0, 8.0)
4313         prout(_("Warp factor set to %d") % int(game.warpfac))
4314         power = 0.75*game.energy
4315         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4316         dist = max(dist, randreal(math.sqrt(2)))
4317         bugout = course(bearing=randreal(12), distance=dist)        # How dumb!
4318         game.optime = bugout.time(game.warpfac)
4319         game.justin = False
4320         game.inorbit = False
4321         warp(bugout, involuntary=True)
4322         if not game.justin:
4323             # This is bad news, we didn't leave quadrant.
4324             if game.alldone:
4325                 return
4326             skip(1)
4327             prout(_("Insufficient energy to leave quadrant."))
4328             finish(FSNOVAED)
4329             return
4330         # Repeat if another snova
4331         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4332             break
4333     if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)==0:
4334         finish(FWON) # Snova killed remaining enemy.
4335
4336 def timwrp():
4337     "Let's do the time warp again."
4338     prout(_("***TIME WARP ENTERED."))
4339     if game.state.snap and withprob(0.5):
4340         # Go back in time
4341         prout(_("You are traveling backwards in time %d stardates.") %
4342               int(game.state.date-game.snapsht.date))
4343         game.state = game.snapsht
4344         game.state.snap = False
4345         if len(game.state.kcmdr):
4346             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4347             schedule(FBATTAK, expran(0.3*game.intime))
4348         schedule(FSNOVA, expran(0.5*game.intime))
4349         # next snapshot will be sooner
4350         schedule(FSNAP, expran(0.25*game.state.remtime))
4351
4352         if game.state.nscrem:
4353             schedule(FSCMOVE, 0.2777)
4354         game.isatb = 0
4355         unschedule(FCDBAS)
4356         unschedule(FSCDBAS)
4357         game.battle.invalidate()
4358         # Make sure Galileo is consistant -- Snapshot may have been taken
4359         # when on planet, which would give us two Galileos!
4360         gotit = False
4361         for l in range(game.inplan):
4362             if game.state.planets[l].known == "shuttle_down":
4363                 gotit = True
4364                 if game.iscraft == "onship" and game.ship=='E':
4365                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4366                     game.iscraft = "offship"
4367         # Likewise, if in the original time the Galileo was abandoned, but
4368         # was on ship earlier, it would have vanished -- let's restore it.
4369         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4370             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4371             game.iscraft = "onship"
4372         # There used to be code to do the actual reconstrction here,
4373         # but the starchart is now part of the snapshotted galaxy state.
4374         prout(_("Spock has reconstructed a correct star chart from memory"))
4375     else:
4376         # Go forward in time
4377         game.optime = expran(0.5*game.intime)
4378         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4379         # cheat to make sure no tractor beams occur during time warp
4380         postpone(FTBEAM, game.optime)
4381         game.damage[DRADIO] += game.optime
4382     newqad()
4383     events()        # Stas Sergeev added this -- do pending events
4384
4385 def probe():
4386     "Launch deep-space probe."
4387     # New code to launch a deep space probe
4388     if game.nprobes == 0:
4389         scanner.chew()
4390         skip(1)
4391         if game.ship == 'E':
4392             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4393         else:
4394             prout(_("Ye Faerie Queene has no deep space probes."))
4395         return
4396     if damaged(DDSP):
4397         scanner.chew()
4398         skip(1)
4399         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4400         return
4401     if is_scheduled(FDSPROB):
4402         scanner.chew()
4403         skip(1)
4404         if damaged(DRADIO) and game.condition != "docked":
4405             prout(_("Spock-  \"Records show the previous probe has not yet"))
4406             prout(_("   reached its destination.\""))
4407         else:
4408             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4409         return
4410     key = scanner.nexttok()
4411     if key == "IHEOL":
4412         if game.nprobes == 1:
4413             prout(_("1 probe left."))
4414         else:
4415             prout(_("%d probes left") % game.nprobes)
4416         proutn(_("Are you sure you want to fire a probe? "))
4417         if not ja():
4418             return
4419     game.isarmed = False
4420     if key == "IHALPHA" and scanner.token == "armed":
4421         game.isarmed = True
4422         key = scanner.nexttok()
4423     elif key == "IHEOL":
4424         proutn(_("Arm NOVAMAX warhead? "))
4425         game.isarmed = ja()
4426     elif key == "IHREAL":                # first element of course
4427         scanner.push(scanner.token)
4428     try:
4429         game.probe = getcourse(isprobe=True)
4430     except TrekError:
4431         return
4432     game.nprobes -= 1
4433     schedule(FDSPROB, 0.01) # Time to move one sector
4434     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4435     game.ididit = True
4436     return
4437
4438 def mayday():
4439     "Yell for help from nearest starbase."
4440     # There's more than one way to move in this game!
4441     scanner.chew()
4442     # Test for conditions which prevent calling for help
4443     if game.condition == "docked":
4444         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4445         return
4446     if damaged(DRADIO):
4447         prout(_("Subspace radio damaged."))
4448         return
4449     if not game.state.baseq:
4450         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4451         return
4452     if game.landed:
4453         prout(_("You must be aboard the %s.") % crmshp())
4454         return
4455     # OK -- call for help from nearest starbase
4456     game.nhelp += 1
4457     if game.base.i!=0:
4458         # There's one in this quadrant
4459         ddist = (game.base - game.sector).distance()
4460     else:
4461         ibq = None      # Force base-quadrant game to persist past loop
4462         ddist = FOREVER
4463         for ibq in game.state.baseq:
4464             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4465             if xdist < ddist:
4466                 ddist = xdist
4467         if ibq is None:
4468             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4469             return
4470         # Since starbase not in quadrant, set up new quadrant
4471         game.quadrant = ibq
4472         newqad()
4473     # dematerialize starship
4474     game.quad[game.sector.i][game.sector.j]='.'
4475     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4476            % (game.quadrant, crmshp()))
4477     game.sector.invalidate()
4478     for m in range(1, 5+1):
4479         w = game.base.scatter()
4480         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4481             # found one -- finish up
4482             game.sector = w
4483             break
4484     if not game.sector.is_valid():
4485         prout(_("You have been lost in space..."))
4486         finish(FMATERIALIZE)
4487         return
4488     # Give starbase three chances to rematerialize starship
4489     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4490     for m in range(1, 3+1):
4491         if m == 1: proutn(_("1st"))
4492         elif m == 2: proutn(_("2nd"))
4493         elif m == 3: proutn(_("3rd"))
4494         proutn(_(" attempt to re-materialize ") + crmshp())
4495         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4496         textcolor(RED)
4497         warble()
4498         if randreal() > probf:
4499             break
4500         prout(_("fails."))
4501         textcolor(DEFAULT)
4502         curses.delay_output(500)
4503     if m > 3:
4504         game.quad[game.sector.i][game.sector.j]='?'
4505         game.alive = False
4506         drawmaps(1)
4507         setwnd(message_window)
4508         finish(FMATERIALIZE)
4509         return
4510     game.quad[game.sector.i][game.sector.j]=game.ship
4511     textcolor(GREEN)
4512     prout(_("succeeds."))
4513     textcolor(DEFAULT)
4514     dock(False)
4515     skip(1)
4516     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4517
4518 def abandon():
4519     "Abandon ship."
4520     scanner.chew()
4521     if game.condition=="docked":
4522         if game.ship!='E':
4523             prout(_("You cannot abandon Ye Faerie Queene."))
4524             return
4525     else:
4526         # Must take shuttle craft to exit
4527         if game.damage[DSHUTTL]==-1:
4528             prout(_("Ye Faerie Queene has no shuttle craft."))
4529             return
4530         if game.damage[DSHUTTL]<0:
4531             prout(_("Shuttle craft now serving Big Macs."))
4532             return
4533         if game.damage[DSHUTTL]>0:
4534             prout(_("Shuttle craft damaged."))
4535             return
4536         if game.landed:
4537             prout(_("You must be aboard the ship."))
4538             return
4539         if game.iscraft != "onship":
4540             prout(_("Shuttle craft not currently available."))
4541             return
4542         # Emit abandon ship messages
4543         skip(1)
4544         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4545         skip(1)
4546         prouts(_("***ALL HANDS ABANDON SHIP!"))
4547         skip(2)
4548         prout(_("Captain and crew escape in shuttle craft."))
4549         if not game.state.baseq:
4550             # Oops! no place to go...
4551             finish(FABANDN)
4552             return
4553         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4554         # Dispose of crew
4555         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4556             prout(_("Remainder of ship's complement beam down"))
4557             prout(_("to nearest habitable planet."))
4558         elif q.planet != None and not damaged(DTRANSP):
4559             prout(_("Remainder of ship's complement beam down to %s.") %
4560                   q.planet)
4561         else:
4562             prout(_("Entire crew of %d left to die in outer space.") %
4563                   game.state.crew)
4564             game.casual += game.state.crew
4565             game.abandoned += game.state.crew
4566         # If at least one base left, give 'em the Faerie Queene
4567         skip(1)
4568         game.icrystl = False # crystals are lost
4569         game.nprobes = 0 # No probes
4570         prout(_("You are captured by Klingons and released to"))
4571         prout(_("the Federation in a prisoner-of-war exchange."))
4572         nb = randrange(len(game.state.baseq))
4573         # Set up quadrant and position FQ adjacient to base
4574         if not game.quadrant == game.state.baseq[nb]:
4575             game.quadrant = game.state.baseq[nb]
4576             game.sector.i = game.sector.j = 5
4577             newqad()
4578         while True:
4579             # position next to base by trial and error
4580             game.quad[game.sector.i][game.sector.j] = '.'
4581             l = QUADSIZE
4582             for l in range(QUADSIZE):
4583                 game.sector = game.base.scatter()
4584                 if game.sector.valid_sector() and \
4585                        game.quad[game.sector.i][game.sector.j] == '.':
4586                     break
4587             if l < QUADSIZE:
4588                 break # found a spot
4589             game.sector.i=QUADSIZE/2
4590             game.sector.j=QUADSIZE/2
4591             newqad()
4592     # Get new commission
4593     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4594     game.state.crew = FULLCREW
4595     prout(_("Starfleet puts you in command of another ship,"))
4596     prout(_("the Faerie Queene, which is antiquated but,"))
4597     prout(_("still useable."))
4598     if game.icrystl:
4599         prout(_("The dilithium crystals have been moved."))
4600     game.imine = False
4601     game.iscraft = "offship" # Galileo disappears
4602     # Resupply ship
4603     game.condition="docked"
4604     for l in range(NDEVICES):
4605         game.damage[l] = 0.0
4606     game.damage[DSHUTTL] = -1
4607     game.energy = game.inenrg = 3000.0
4608     game.shield = game.inshld = 1250.0
4609     game.torps = game.intorps = 6
4610     game.lsupres=game.inlsr=3.0
4611     game.shldup=False
4612     game.warpfac=5.0
4613     game.brigfree = game.brigcapacity = 300
4614     return
4615
4616 # Code from planets.c begins here.
4617
4618 def consumeTime():
4619     "Abort a lengthy operation if an event interrupts it."
4620     game.ididit = True
4621     events()
4622     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4623         return True
4624     return False
4625
4626 def survey():
4627     "Report on (uninhabited) planets in the galaxy."
4628     iknow = False
4629     skip(1)
4630     scanner.chew()
4631     prout(_("Spock-  \"Planet report follows, Captain.\""))
4632     skip(1)
4633     for i in range(game.inplan):
4634         if game.state.planets[i].pclass == "destroyed":
4635             continue
4636         if (game.state.planets[i].known != "unknown" \
4637             and not game.state.planets[i].inhabited) \
4638             or game.idebug:
4639             iknow = True
4640             if game.idebug and game.state.planets[i].known=="unknown":
4641                 proutn("(Unknown) ")
4642             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4643             proutn(_("   class "))
4644             proutn(game.state.planets[i].pclass)
4645             proutn("   ")
4646             if game.state.planets[i].crystals != "present":
4647                 proutn(_("no "))
4648             prout(_("dilithium crystals present."))
4649             if game.state.planets[i].known=="shuttle_down":
4650                 prout(_("    Shuttle Craft Galileo on surface."))
4651     if not iknow:
4652         prout(_("No information available."))
4653
4654 def orbit():
4655     "Enter standard orbit."
4656     skip(1)
4657     scanner.chew()
4658     if game.inorbit:
4659         prout(_("Already in standard orbit."))
4660         return
4661     if damaged(DWARPEN) and damaged(DIMPULS):
4662         prout(_("Both warp and impulse engines damaged."))
4663         return
4664     if not game.plnet.is_valid():
4665         prout("There is no planet in this sector.")
4666         return
4667     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4668         prout(crmshp() + _(" not adjacent to planet."))
4669         skip(1)
4670         return
4671     game.optime = randreal(0.02, 0.05)
4672     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4673     newcnd()
4674     if consumeTime():
4675         return
4676     game.height = randreal(1400, 8600)
4677     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4678     game.inorbit = True
4679     game.ididit = True
4680
4681 def sensor():
4682     "Examine planets in this quadrant."
4683     if damaged(DSRSENS):
4684         if game.options & OPTION_TTY:
4685             prout(_("Short range sensors damaged."))
4686         return
4687     if game.iplnet is None:
4688         if game.options & OPTION_TTY:
4689             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4690         return
4691     if game.iplnet.known == "unknown":
4692         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4693         skip(1)
4694         prout(_("         Planet at Sector %s is of class %s.") %
4695               (game.plnet, game.iplnet.pclass))
4696         if game.iplnet.known=="shuttle_down":
4697             prout(_("         Sensors show Galileo still on surface."))
4698         proutn(_("         Readings indicate"))
4699         if game.iplnet.crystals != "present":
4700             proutn(_(" no"))
4701         prout(_(" dilithium crystals present.\""))
4702         if game.iplnet.known == "unknown":
4703             game.iplnet.known = "known"
4704     elif game.iplnet.inhabited:
4705         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4706         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4707
4708 def beam():
4709     "Use the transporter."
4710     nrgneed = 0
4711     scanner.chew()
4712     skip(1)
4713     if damaged(DTRANSP):
4714         prout(_("Transporter damaged."))
4715         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4716             skip(1)
4717             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4718             if ja():
4719                 shuttle()
4720         return
4721     if not game.inorbit:
4722         prout(crmshp() + _(" not in standard orbit."))
4723         return
4724     if game.shldup:
4725         prout(_("Impossible to transport through shields."))
4726         return
4727     if game.iplnet.known=="unknown":
4728         prout(_("Spock-  \"Captain, we have no information on this planet"))
4729         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4730         prout(_("  you may not go down.\""))
4731         return
4732     if not game.landed and game.iplnet.crystals=="absent":
4733         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4734         prout(_("  exploring a planet with no dilithium crystals."))
4735         proutn(_("  Are you sure this is wise?\" "))
4736         if not ja():
4737             scanner.chew()
4738             return
4739     if not (game.options & OPTION_PLAIN):
4740         nrgneed = 50 * game.skill + game.height / 100.0
4741         if nrgneed > game.energy:
4742             prout(_("Engineering to bridge--"))
4743             prout(_("  Captain, we don't have enough energy for transportation."))
4744             return
4745         if not game.landed and nrgneed * 2 > game.energy:
4746             prout(_("Engineering to bridge--"))
4747             prout(_("  Captain, we have enough energy only to transport you down to"))
4748             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4749             if game.iplnet.known == "shuttle_down":
4750                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4751             proutn(_("  Are you sure this is wise?\" "))
4752             if not ja():
4753                 scanner.chew()
4754                 return
4755     if game.landed:
4756         # Coming from planet
4757         if game.iplnet.known=="shuttle_down":
4758             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4759             if ja():
4760                 scanner.chew()
4761                 return
4762             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4763         prout(_("Landing party assembled, ready to beam up."))
4764         skip(1)
4765         prout(_("Kirk whips out communicator..."))
4766         prouts(_("BEEP  BEEP  BEEP"))
4767         skip(2)
4768         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4769     else:
4770         # Going to planet
4771         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4772         skip(1)
4773         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4774         skip(1)
4775         prout(_("Kirk-  \"Energize.\""))
4776     game.ididit = True
4777     skip(1)
4778     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4779     skip(2)
4780     if not withprob(0.98):
4781         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4782         skip(2)
4783         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4784         finish(FLOST)
4785         return
4786     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4787     game.landed = not game.landed
4788     game.energy -= nrgneed
4789     skip(2)
4790     prout(_("Transport complete."))
4791     if game.landed and game.iplnet.known=="shuttle_down":
4792         prout(_("The shuttle craft Galileo is here!"))
4793     if not game.landed and game.imine:
4794         game.icrystl = True
4795         game.cryprob = 0.05
4796     game.imine = False
4797     return
4798
4799 def mine():
4800     "Strip-mine a world for dilithium."
4801     skip(1)
4802     scanner.chew()
4803     if not game.landed:
4804         prout(_("Mining party not on planet."))
4805         return
4806     if game.iplnet.crystals == "mined":
4807         prout(_("This planet has already been strip-mined for dilithium."))
4808         return
4809     elif game.iplnet.crystals == "absent":
4810         prout(_("No dilithium crystals on this planet."))
4811         return
4812     if game.imine:
4813         prout(_("You've already mined enough crystals for this trip."))
4814         return
4815     if game.icrystl and game.cryprob == 0.05:
4816         prout(_("With all those fresh crystals aboard the ") + crmshp())
4817         prout(_("there's no reason to mine more at this time."))
4818         return
4819     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4820     if consumeTime():
4821         return
4822     prout(_("Mining operation complete."))
4823     game.iplnet.crystals = "mined"
4824     game.imine = game.ididit = True
4825
4826 def usecrystals():
4827     "Use dilithium crystals."
4828     game.ididit = False
4829     skip(1)
4830     scanner.chew()
4831     if not game.icrystl:
4832         prout(_("No dilithium crystals available."))
4833         return
4834     if game.energy >= 1000:
4835         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4836         prout(_("  except when Condition Yellow exists."))
4837         return
4838     prout(_("Spock- \"Captain, I must warn you that loading"))
4839     prout(_("  raw dilithium crystals into the ship's power"))
4840     prout(_("  system may risk a severe explosion."))
4841     proutn(_("  Are you sure this is wise?\" "))
4842     if not ja():
4843         scanner.chew()
4844         return
4845     skip(1)
4846     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4847     prout(_("  Mr. Spock and I will try it.\""))
4848     skip(1)
4849     prout(_("Spock-  \"Crystals in place, Sir."))
4850     prout(_("  Ready to activate circuit.\""))
4851     skip(1)
4852     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4853     skip(1)
4854     if withprob(game.cryprob):
4855         prouts(_("  \"Activating now! - - No good!  It's***"))
4856         skip(2)
4857         prouts(_("***RED ALERT!  RED A*L********************************"))
4858         skip(1)
4859         stars()
4860         prouts(_("******************   KA-BOOM!!!!   *******************"))
4861         skip(1)
4862         kaboom()
4863         return
4864     game.energy += randreal(5000.0, 5500.0)
4865     prouts(_("  \"Activating now! - - "))
4866     prout(_("The instruments"))
4867     prout(_("   are going crazy, but I think it's"))
4868     prout(_("   going to work!!  Congratulations, Sir!\""))
4869     game.cryprob *= 2.0
4870     game.ididit = True
4871
4872 def shuttle():
4873     "Use shuttlecraft for planetary jaunt."
4874     scanner.chew()
4875     skip(1)
4876     if damaged(DSHUTTL):
4877         if game.damage[DSHUTTL] == -1.0:
4878             if game.inorbit and game.iplnet.known == "shuttle_down":
4879                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4880             else:
4881                 prout(_("Ye Faerie Queene had no shuttle craft."))
4882         elif game.damage[DSHUTTL] > 0:
4883             prout(_("The Galileo is damaged."))
4884         else: # game.damage[DSHUTTL] < 0
4885             prout(_("Shuttle craft is now serving Big Macs."))
4886         return
4887     if not game.inorbit:
4888         prout(crmshp() + _(" not in standard orbit."))
4889         return
4890     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4891         prout(_("Shuttle craft not currently available."))
4892         return
4893     if not game.landed and game.iplnet.known=="shuttle_down":
4894         prout(_("You will have to beam down to retrieve the shuttle craft."))
4895         return
4896     if game.shldup or game.condition == "docked":
4897         prout(_("Shuttle craft cannot pass through shields."))
4898         return
4899     if game.iplnet.known=="unknown":
4900         prout(_("Spock-  \"Captain, we have no information on this planet"))
4901         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4902         prout(_("  you may not fly down.\""))
4903         return
4904     game.optime = 3.0e-5*game.height
4905     if game.optime >= 0.8*game.state.remtime:
4906         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4907         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4908                int(100*game.optime/game.state.remtime))
4909         prout(_("remaining time."))
4910         proutn(_("Are you sure this is wise?\" "))
4911         if not ja():
4912             game.optime = 0.0
4913             return
4914     if game.landed:
4915         # Kirk on planet
4916         if game.iscraft == "onship":
4917             # Galileo on ship!
4918             if not damaged(DTRANSP):
4919                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4920                 if ja():
4921                     beam()
4922                     return
4923                 proutn(_("Shuttle crew"))
4924             else:
4925                 proutn(_("Rescue party"))
4926             prout(_(" boards Galileo and swoops toward planet surface."))
4927             game.iscraft = "offship"
4928             skip(1)
4929             if consumeTime():
4930                 return
4931             game.iplnet.known="shuttle_down"
4932             prout(_("Trip complete."))
4933             return
4934         else:
4935             # Ready to go back to ship
4936             prout(_("You and your mining party board the"))
4937             prout(_("shuttle craft for the trip back to the Enterprise."))
4938             skip(1)
4939             prouts(_("The short hop begins . . ."))
4940             skip(1)
4941             game.iplnet.known="known"
4942             game.icraft = True
4943             skip(1)
4944             game.landed = False
4945             if consumeTime():
4946                 return
4947             game.iscraft = "onship"
4948             game.icraft = False
4949             if game.imine:
4950                 game.icrystl = True
4951                 game.cryprob = 0.05
4952             game.imine = False
4953             prout(_("Trip complete."))
4954             return
4955     else:
4956         # Kirk on ship and so is Galileo
4957         prout(_("Mining party assembles in the hangar deck,"))
4958         prout(_("ready to board the shuttle craft \"Galileo\"."))
4959         skip(1)
4960         prouts(_("The hangar doors open; the trip begins."))
4961         skip(1)
4962         game.icraft = True
4963         game.iscraft = "offship"
4964         if consumeTime():
4965             return
4966         game.iplnet.known = "shuttle_down"
4967         game.landed = True
4968         game.icraft = False
4969         prout(_("Trip complete."))
4970         return
4971
4972 def deathray():
4973     "Use the big zapper."
4974     game.ididit = False
4975     skip(1)
4976     scanner.chew()
4977     if game.ship != 'E':
4978         prout(_("Ye Faerie Queene has no death ray."))
4979         return
4980     if len(game.enemies)==0:
4981         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
4982         return
4983     if damaged(DDRAY):
4984         prout(_("Death Ray is damaged."))
4985         return
4986     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
4987     prout(_("  is highly unpredictible.  Considering the alternatives,"))
4988     proutn(_("  are you sure this is wise?\" "))
4989     if not ja():
4990         return
4991     prout(_("Spock-  \"Acknowledged.\""))
4992     skip(1)
4993     game.ididit = True
4994     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
4995     skip(1)
4996     prout(_("Crew scrambles in emergency preparation."))
4997     prout(_("Spock and Scotty ready the death ray and"))
4998     prout(_("prepare to channel all ship's power to the device."))
4999     skip(1)
5000     prout(_("Spock-  \"Preparations complete, sir.\""))
5001     prout(_("Kirk-  \"Engage!\""))
5002     skip(1)
5003     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5004     skip(1)
5005     dprob = 0.30
5006     if game.options & OPTION_PLAIN:
5007         dprob = 0.5
5008     r = randreal()
5009     if r > dprob:
5010         prouts(_("Sulu- \"Captain!  It's working!\""))
5011         skip(2)
5012         while len(game.enemies) > 0:
5013             deadkl(game.enemies[1].location, game.quad[game.enemies[1].location.i][game.enemies[1].location.j],game.enemies[1].location)
5014         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5015         if (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem) == 0:
5016             finish(FWON)
5017         if (game.options & OPTION_PLAIN) == 0:
5018             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5019             if withprob(0.05):
5020                 prout(_("   is still operational.\""))
5021             else:
5022                 prout(_("   has been rendered nonfunctional.\""))
5023                 game.damage[DDRAY] = 39.95
5024         return
5025     r = randreal()        # Pick failure method
5026     if r <= 0.30:
5027         prouts(_("Sulu- \"Captain!  It's working!\""))
5028         skip(1)
5029         prouts(_("***RED ALERT!  RED ALERT!"))
5030         skip(1)
5031         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5032         skip(1)
5033         prouts(_("***RED ALERT!  RED A*L********************************"))
5034         skip(1)
5035         stars()
5036         prouts(_("******************   KA-BOOM!!!!   *******************"))
5037         skip(1)
5038         kaboom()
5039         return
5040     if r <= 0.55:
5041         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5042         skip(1)
5043         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5044         skip(1)
5045         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5046         prout(_("  have apparently been transformed into strange mutations."))
5047         prout(_("  Vulcans do not seem to be affected."))
5048         skip(1)
5049         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5050         finish(FDRAY)
5051         return
5052     if r <= 0.75:
5053         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5054         skip(2)
5055         proutn(_("Spock-  \"I believe the word is"))
5056         prouts(_(" *ASTONISHING*"))
5057         prout(_(" Mr. Sulu."))
5058         for i in range(QUADSIZE):
5059             for j in range(QUADSIZE):
5060                 if game.quad[i][j] == '.':
5061                     game.quad[i][j] = '?'
5062         prout(_("  Captain, our quadrant is now infested with"))
5063         prouts(_(" - - - - - -  *THINGS*."))
5064         skip(1)
5065         prout(_("  I have no logical explanation.\""))
5066         return
5067     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5068     skip(1)
5069     prout(_("Scotty-  \"There are so many tribbles down here"))
5070     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5071     finish(FTRIBBLE)
5072     return
5073
5074 # Code from reports.c begins here
5075
5076 def attackreport(curt):
5077     "eport status of bases under attack."
5078     if not curt:
5079         if is_scheduled(FCDBAS):
5080             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5081             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5082         elif game.isatb == 1:
5083             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5084             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5085         else:
5086             prout(_("No Starbase is currently under attack."))
5087     else:
5088         if is_scheduled(FCDBAS):
5089             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5090         if game.isatb:
5091             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5092         clreol()
5093
5094 def report():
5095     # report on general game status
5096     scanner.chew()
5097     s1 = (game.thawed and _("thawed ")) or ""
5098     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5099     s3 = (None, _("novice"), _("fair"),
5100           _("good"), _("expert"), _("emeritus"))[game.skill]
5101     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5102     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5103         prout(_("No plaque is allowed."))
5104     if game.tourn:
5105         prout(_("This is tournament game %d.") % game.tourn)
5106     prout(_("Your secret password is \"%s\"") % game.passwd)
5107     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - (game.state.remkl + len(game.state.kcmdr) + game.state.nscrem)),
5108                                                       (game.inkling + game.incom + game.inscom)))
5109     if game.incom - len(game.state.kcmdr):
5110         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5111     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
5112         prout(_(", but no Commanders."))
5113     else:
5114         prout(".")
5115     if game.skill > SKILL_FAIR:
5116         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5117     if len(game.state.baseq) != game.inbase:
5118         proutn(_("There "))
5119         if game.inbase-len(game.state.baseq)==1:
5120             proutn(_("has been 1 base"))
5121         else:
5122             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5123         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5124     else:
5125         prout(_("There are %d bases.") % game.inbase)
5126     if communicating() or game.iseenit:
5127         # Don't report this if not seen and
5128         # either the radio is dead or not at base!
5129         attackreport(False)
5130         game.iseenit = True
5131     if game.casual:
5132         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5133     if game.brigcapacity != game.brigfree:
5134         embriggened = brigcapacity-brigfree
5135         if embriggened == 1:
5136             prout(_("1 Klingon in brig"))
5137         else:
5138             prout(_("%d Klingons in brig.") %  embriggened)
5139         if game.kcaptured == 0:
5140             pass
5141         elif game.kcaptured == 1:
5142             prout(_("1 captured Klingon turned in to Starfleet."))
5143         else:
5144             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5145     if game.nhelp:
5146         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5147     if game.ship == 'E':
5148         proutn(_("You have "))
5149         if game.nprobes:
5150             proutn("%d" % (game.nprobes))
5151         else:
5152             proutn(_("no"))
5153         proutn(_(" deep space probe"))
5154         if game.nprobes!=1:
5155             proutn(_("s"))
5156         prout(".")
5157     if communicating() and is_scheduled(FDSPROB):
5158         if game.isarmed:
5159             proutn(_("An armed deep space probe is in "))
5160         else:
5161             proutn(_("A deep space probe is in "))
5162         prout("Quadrant %s." % game.probe.quadrant())
5163     if game.icrystl:
5164         if game.cryprob <= .05:
5165             prout(_("Dilithium crystals aboard ship... not yet used."))
5166         else:
5167             i=0
5168             ai = 0.05
5169             while game.cryprob > ai:
5170                 ai *= 2.0
5171                 i += 1
5172             prout(_("Dilithium crystals have been used %d time%s.") % \
5173                   (i, (_("s"), "")[i==1]))
5174     skip(1)
5175
5176 def lrscan(silent):
5177     "Long-range sensor scan."
5178     if damaged(DLRSENS):
5179         # Now allow base's sensors if docked
5180         if game.condition != "docked":
5181             if not silent:
5182                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5183             return
5184         if not silent:
5185             prout(_("Starbase's long-range scan"))
5186     elif not silent:
5187         prout(_("Long-range scan"))
5188     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5189         if not silent:
5190             proutn(" ")
5191         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5192             if not Coord(x, y).valid_quadrant():
5193                 if not silent:
5194                     proutn("  -1")
5195             else:
5196                 if not damaged(DRADIO):
5197                     game.state.galaxy[x][y].charted = True
5198                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5199                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5200                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5201                 if not silent and game.state.galaxy[x][y].supernova:
5202                     proutn(" ***")
5203                 elif not silent:
5204                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
5205         if not silent:
5206             prout(" ")
5207
5208 def damagereport():
5209     "Damage report."
5210     jdam = False
5211     scanner.chew()
5212     for i in range(NDEVICES):
5213         if damaged(i):
5214             if not jdam:
5215                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5216                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5217                 jdam = True
5218             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5219                                                game.damage[i]+0.05,
5220                                                DOCKFAC*game.damage[i]+0.005))
5221     if not jdam:
5222         prout(_("All devices functional."))
5223
5224 def rechart():
5225     "Update the chart in the Enterprise's computer from galaxy data."
5226     game.lastchart = game.state.date
5227     for i in range(GALSIZE):
5228         for j in range(GALSIZE):
5229             if game.state.galaxy[i][j].charted:
5230                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5231                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5232                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5233
5234 def chart():
5235     "Display the star chart."
5236     scanner.chew()
5237     if (game.options & OPTION_AUTOSCAN):
5238         lrscan(silent=True)
5239     if not damaged(DRADIO):
5240         rechart()
5241     if game.lastchart < game.state.date and game.condition == "docked":
5242         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5243         rechart()
5244     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5245     if game.state.date > game.lastchart:
5246         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5247     prout("      1    2    3    4    5    6    7    8")
5248     for i in range(GALSIZE):
5249         proutn("%d |" % (i+1))
5250         for j in range(GALSIZE):
5251             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5252                 proutn("<")
5253             else:
5254                 proutn(" ")
5255             if game.state.galaxy[i][j].supernova:
5256                 show = "***"
5257             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5258                 show = ".1."
5259             elif game.state.galaxy[i][j].charted:
5260                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5261             else:
5262                 show = "..."
5263             proutn(show)
5264             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5265                 proutn(">")
5266             else:
5267                 proutn(" ")
5268         proutn("  |")
5269         if i<GALSIZE:
5270             skip(1)
5271
5272 def sectscan(goodScan, i, j):
5273     "Light up an individual dot in a sector."
5274     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5275         if game.quad[i][j] in ('E', 'F'):
5276             textcolor({"green":GREEN,
5277                        "yellow":YELLOW,
5278                        "red":RED,
5279                        "docked":CYAN,
5280                        "dead":BROWN}[game.condition])
5281         else:
5282             textcolor({'?':LIGHTMAGENTA,
5283                        'K':LIGHTRED,
5284                        'S':LIGHTRED,
5285                        'C':LIGHTRED,
5286                        'R':LIGHTRED,
5287                        'T':LIGHTRED,
5288                        }.get(game.quad[i][j], DEFAULT))
5289         if game.iscloaked:
5290             highvideo()
5291         proutn("%c " % game.quad[i][j])
5292         textcolor(DEFAULT)
5293     else:
5294         proutn("- ")
5295
5296 def status(req=0):
5297     "Emit status report lines"
5298     if not req or req == 1:
5299         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5300                % (game.state.date, game.state.remtime))
5301     if not req or req == 2:
5302         if game.condition != "docked":
5303             newcnd()
5304         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5305                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5306         if game.iscloaked:
5307             prout(_(", CLOAKED"))
5308     if not req or req == 3:
5309         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5310     if not req or req == 4:
5311         if damaged(DLIFSUP):
5312             if game.condition == "docked":
5313                 s = _("DAMAGED, Base provides")
5314             else:
5315                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5316         else:
5317             s = _("ACTIVE")
5318         prstat(_("Life Support"), s)
5319     if not req or req == 5:
5320         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5321     if not req or req == 6:
5322         extra = ""
5323         if game.icrystl and (game.options & OPTION_SHOWME):
5324             extra = _(" (have crystals)")
5325         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5326     if not req or req == 7:
5327         prstat(_("Torpedoes"), "%d" % (game.torps))
5328     if not req or req == 8:
5329         if damaged(DSHIELD):
5330             s = _("DAMAGED,")
5331         elif game.shldup:
5332             s = _("UP,")
5333         else:
5334             s = _("DOWN,")
5335         data = _(" %d%% %.1f units") \
5336                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5337         prstat(_("Shields"), s+data)
5338     if not req or req == 9:
5339         prstat(_("Klingons Left"), "%d" \
5340                % (game.state.remkl+len(game.state.kcmdr)+game.state.nscrem))
5341     if not req or req == 10:
5342         if game.options & OPTION_WORLDS:
5343             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5344             if plnet and plnet.inhabited:
5345                 prstat(_("Major system"), plnet.name)
5346             else:
5347                 prout(_("Sector is uninhabited"))
5348     elif not req or req == 11:
5349         attackreport(not req)
5350
5351 def request():
5352     "Request specified status data, a historical relic from slow TTYs."
5353     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5354     while scanner.nexttok() == "IHEOL":
5355         proutn(_("Information desired? "))
5356     scanner.chew()
5357     if scanner.token in requests:
5358         status(requests.index(scanner.token))
5359     else:
5360         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5361         prout(("  date, condition, position, lsupport, warpfactor,"))
5362         prout(("  energy, torpedoes, shields, klingons, system, time."))
5363
5364 def srscan():
5365     "Short-range scan."
5366     goodScan=True
5367     if damaged(DSRSENS):
5368         # Allow base's sensors if docked
5369         if game.condition != "docked":
5370             prout(_("   S.R. SENSORS DAMAGED!"))
5371             goodScan=False
5372         else:
5373             prout(_("  [Using Base's sensors]"))
5374     else:
5375         prout(_("     Short-range scan"))
5376     if goodScan and not damaged(DRADIO):
5377         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5378         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5379         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5380         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5381     prout("    1 2 3 4 5 6 7 8 9 10")
5382     if game.condition != "docked":
5383         newcnd()
5384     for i in range(QUADSIZE):
5385         proutn("%2d  " % (i+1))
5386         for j in range(QUADSIZE):
5387             sectscan(goodScan, i, j)
5388         skip(1)
5389
5390 def eta():
5391     "Use computer to get estimated time of arrival for a warp jump."
5392     w1 = Coord(); w2 = Coord()
5393     prompt = False
5394     if damaged(DCOMPTR):
5395         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5396         skip(1)
5397         return
5398     if scanner.nexttok() != "IHREAL":
5399         prompt = True
5400         scanner.chew()
5401         proutn(_("Destination quadrant and/or sector? "))
5402         if scanner.nexttok()!="IHREAL":
5403             huh()
5404             return
5405     w1.j = int(scanner.real-0.5)
5406     if scanner.nexttok() != "IHREAL":
5407         huh()
5408         return
5409     w1.i = int(scanner.real-0.5)
5410     if scanner.nexttok() == "IHREAL":
5411         w2.j = int(scanner.real-0.5)
5412         if scanner.nexttok() != "IHREAL":
5413             huh()
5414             return
5415         w2.i = int(scanner.real-0.5)
5416     else:
5417         if game.quadrant.j>w1.i:
5418             w2.i = 0
5419         else:
5420             w2.i=QUADSIZE-1
5421         if game.quadrant.i>w1.j:
5422             w2.j = 0
5423         else:
5424             w2.j=QUADSIZE-1
5425     if not w1.valid_quadrant() or not w2.valid_sector():
5426         huh()
5427         return
5428     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5429                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5430     wfl = False
5431     if prompt:
5432         prout(_("Answer \"no\" if you don't know the value:"))
5433     while True:
5434         scanner.chew()
5435         proutn(_("Time or arrival date? "))
5436         if scanner.nexttok()=="IHREAL":
5437             ttime = scanner.real
5438             if ttime > game.state.date:
5439                 ttime -= game.state.date # Actually a star date
5440             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5441             if ttime <= 1e-10 or twarp > 10:
5442                 prout(_("We'll never make it, sir."))
5443                 scanner.chew()
5444                 return
5445             if twarp < 1.0:
5446                 twarp = 1.0
5447             break
5448         scanner.chew()
5449         proutn(_("Warp factor? "))
5450         if scanner.nexttok()== "IHREAL":
5451             wfl = True
5452             twarp = scanner.real
5453             if twarp<1.0 or twarp > 10.0:
5454                 huh()
5455                 return
5456             break
5457         prout(_("Captain, certainly you can give me one of these."))
5458     while True:
5459         scanner.chew()
5460         ttime = (10.0*dist)/twarp**2
5461         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5462         if tpower >= game.energy:
5463             prout(_("Insufficient energy, sir."))
5464             if not game.shldup or tpower > game.energy*2.0:
5465                 if not wfl:
5466                     return
5467                 proutn(_("New warp factor to try? "))
5468                 if scanner.nexttok() == "IHREAL":
5469                     wfl = True
5470                     twarp = scanner.real
5471                     if twarp<1.0 or twarp > 10.0:
5472                         huh()
5473                         return
5474                     continue
5475                 else:
5476                     scanner.chew()
5477                     skip(1)
5478                     return
5479             prout(_("But if you lower your shields,"))
5480             proutn(_("remaining"))
5481             tpower /= 2
5482         else:
5483             proutn(_("Remaining"))
5484         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5485         if wfl:
5486             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5487         elif twarp==1.0:
5488             prout(_("Any warp speed is adequate."))
5489         else:
5490             prout(_("Minimum warp needed is %.2f,") % (twarp))
5491             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5492         if game.state.remtime < ttime:
5493             prout(_("Unfortunately, the Federation will be destroyed by then."))
5494         if twarp > 6.0:
5495             prout(_("You'll be taking risks at that speed, Captain"))
5496         if (game.isatb==1 and game.state.kscmdr == w1 and \
5497              scheduled(FSCDBAS)< ttime+game.state.date) or \
5498             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5499             prout(_("The starbase there will be destroyed by then."))
5500         proutn(_("New warp factor to try? "))
5501         if scanner.nexttok() == "IHREAL":
5502             wfl = True
5503             twarp = scanner.real
5504             if twarp<1.0 or twarp > 10.0:
5505                 huh()
5506                 return
5507         else:
5508             scanner.chew()
5509             skip(1)
5510             return
5511
5512 # Code from setup.c begins here
5513
5514 def prelim():
5515     "Issue a historically correct banner."
5516     skip(2)
5517     prout(_("-SUPER- STAR TREK"))
5518     skip(1)
5519 # From the FORTRAN original
5520 #    prout(_("Latest update-21 Sept 78"))
5521 #    skip(1)
5522
5523 def freeze(boss):
5524     "Save game."
5525     if boss:
5526         scanner.push("emsave.trk")
5527     key = scanner.nexttok()
5528     if key == "IHEOL":
5529         proutn(_("File name: "))
5530         key = scanner.nexttok()
5531     if key != "IHALPHA":
5532         huh()
5533         return
5534     if '.' not in scanner.token:
5535         scanner.token += ".trk"
5536     try:
5537         fp = open(scanner.token, "wb")
5538     except IOError:
5539         prout(_("Can't freeze game as file %s") % scanner.token)
5540         return
5541     pickle.dump(game, fp)
5542     fp.close()
5543     scanner.chew()
5544
5545 def thaw():
5546     "Retrieve saved game."
5547     global game
5548     game.passwd = None
5549     key = scanner.nexttok()
5550     if key == "IHEOL":
5551         proutn(_("File name: "))
5552         key = scanner.nexttok()
5553     if key != "IHALPHA":
5554         huh()
5555         return True
5556     if '.' not in scanner.token:
5557         scanner.token += ".trk"
5558     try:
5559         fp = open(scanner.token, "rb")
5560     except IOError:
5561         prout(_("Can't thaw game in %s") % scanner.token)
5562         return
5563     game = pickle.load(fp)
5564     fp.close()
5565     scanner.chew()
5566     return False
5567
5568 # I used <http://www.memory-alpha.org> to find planets
5569 # with references in ST:TOS.  Earth and the Alpha Centauri
5570 # Colony have been omitted.
5571 #
5572 # Some planets marked Class G and P here will be displayed as class M
5573 # because of the way planets are generated. This is a known bug.
5574 systnames = (
5575     # Federation Worlds
5576     _("Andoria (Fesoan)"),        # several episodes
5577     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5578     _("Vulcan (T'Khasi)"),        # many episodes
5579     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5580     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5581     _("Ardana"),                # TOS: "The Cloud Minders"
5582     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5583     _("Gideon"),                # TOS: "The Mark of Gideon"
5584     _("Aldebaran III"),                # TOS: "The Deadly Years"
5585     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5586     _("Altair IV"),                # TOS: "Amok Time
5587     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5588     _("Benecia"),                # TOS: "The Conscience of the King"
5589     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5590     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5591     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5592     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5593     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5594     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5595     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5596     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5597     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5598     _("Janus IV"),                # TOS: "The Devil in the Dark"
5599     _("Makus III"),                # TOS: "The Galileo Seven"
5600     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5601     _("Omega IV"),                # TOS: "The Omega Glory"
5602     _("Regulus V"),                # TOS: "Amok Time
5603     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5604     # Worlds from BSD Trek
5605     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5606     _("Beta III"),                # TOS: "The Return of the Archons"
5607     _("Triacus"),                # TOS: "And the Children Shall Lead",
5608     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5609     #        # Others
5610     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5611     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5612     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5613     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5614     #    _("Tiburon"),                # TOS: "The Way to Eden"
5615     #    _("Merak II"),                # TOS: "The Cloud Minders"
5616     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5617     #    _("Iotia"),                # TOS: "A Piece of the Action"
5618 )
5619
5620 device = (
5621     _("S. R. Sensors"), \
5622     _("L. R. Sensors"), \
5623     _("Phasers"), \
5624     _("Photon Tubes"), \
5625     _("Life Support"), \
5626     _("Warp Engines"), \
5627     _("Impulse Engines"), \
5628     _("Shields"), \
5629     _("Subspace Radio"), \
5630     _("Shuttle Craft"), \
5631     _("Computer"), \
5632     _("Navigation System"), \
5633     _("Transporter"), \
5634     _("Shield Control"), \
5635     _("Death Ray"), \
5636     _("D. S. Probe"), \
5637     _("Cloaking Device"), \
5638 )
5639
5640 def setup():
5641     "Prepare to play, set up cosmos."
5642     w = Coord()
5643     #  Decide how many of everything
5644     if choose():
5645         return # frozen game
5646     # Prepare the Enterprise
5647     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5648     game.ship = 'E'
5649     game.state.crew = FULLCREW
5650     game.energy = game.inenrg = 5000.0
5651     game.shield = game.inshld = 2500.0
5652     game.inlsr = 4.0
5653     game.lsupres = 4.0
5654     game.quadrant = randplace(GALSIZE)
5655     game.sector = randplace(QUADSIZE)
5656     game.torps = game.intorps = 10
5657     game.nprobes = randrange(2, 5)
5658     game.warpfac = 5.0
5659     for i in range(NDEVICES):
5660         game.damage[i] = 0.0
5661     # Set up assorted game parameters
5662     game.battle = Coord()
5663     game.state.date = game.indate = 100.0 * randreal(20, 51)
5664     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5665     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5666     game.isatb = game.state.nplankl = 0
5667     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5668     game.iscraft = "onship"
5669     game.landed = False
5670     game.alive = True
5671
5672     # the galaxy
5673     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5674     # the starchart
5675     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5676
5677     game.state.planets = []      # Planet information
5678     game.state.baseq = []      # Base quadrant coordinates
5679     game.state.kcmdr = []      # Commander quadrant coordinates
5680     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5681
5682     # Starchart is functional but we've never seen it
5683     game.lastchart = FOREVER
5684     # Put stars in the galaxy
5685     game.instar = 0
5686     for i in range(GALSIZE):
5687         for j in range(GALSIZE):
5688             # Can't have more stars per quadrant than fit in one decimal digit,
5689             # if we do the chart representation will break.
5690             k = randrange(1, min(10, QUADSIZE**2/10))
5691             game.instar += k
5692             game.state.galaxy[i][j].stars = k
5693     # Locate star bases in galaxy
5694     if game.idebug:
5695         prout("=== Allocating %d bases" % game.inbase)
5696     for i in range(game.inbase):
5697         while True:
5698             while True:
5699                 w = randplace(GALSIZE)
5700                 if not game.state.galaxy[w.i][w.j].starbase:
5701                     break
5702             contflag = False
5703             # C version: for (j = i-1; j > 0; j--)
5704             # so it did them in the opposite order.
5705             for j in range(1, i):
5706                 # Improved placement algorithm to spread out bases
5707                 distq = (w - game.state.baseq[j]).distance()
5708                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5709                     contflag = True
5710                     if game.idebug:
5711                         prout("=== Abandoning base #%d at %s" % (i, w))
5712                     break
5713                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5714                     if game.idebug:
5715                         prout("=== Saving base #%d, close to #%d" % (i, j))
5716             if not contflag:
5717                 break
5718         if game.idebug:
5719             prout("=== Placing base #%d in quadrant %s" % (i, w))
5720         game.state.baseq.append(w)
5721         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5722     # Position ordinary Klingon Battle Cruisers
5723     krem = game.inkling
5724     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5725     if klumper > MAXKLQUAD:
5726         klumper = MAXKLQUAD
5727     while True:
5728         r = randreal()
5729         klump = (1.0 - r*r)*klumper
5730         if klump > krem:
5731             klump = krem
5732         krem -= klump
5733         while True:
5734             w = randplace(GALSIZE)
5735             if not game.state.galaxy[w.i][w.j].supernova and \
5736                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5737                 break
5738         game.state.galaxy[w.i][w.j].klingons += int(klump)
5739         if krem <= 0:
5740             break
5741     # Position Klingon Commander Ships
5742     for i in range(game.incom):
5743         while True:
5744             w = randplace(GALSIZE)
5745             if not welcoming(w) or w in game.state.kcmdr:
5746                 continue
5747             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5748                 break
5749         game.state.galaxy[w.i][w.j].klingons += 1
5750         game.state.kcmdr.append(w)
5751     # Locate planets in galaxy
5752     for i in range(game.inplan):
5753         while True:
5754             w = randplace(GALSIZE)
5755             if game.state.galaxy[w.i][w.j].planet is None:
5756                 break
5757         new = Planet()
5758         new.quadrant = w
5759         new.crystals = "absent"
5760         if (game.options & OPTION_WORLDS) and i < NINHAB:
5761             new.pclass = "M"        # All inhabited planets are class M
5762             new.crystals = "absent"
5763             new.known = "known"
5764             new.name = systnames[i]
5765             new.inhabited = True
5766         else:
5767             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5768             if withprob(0.33):
5769                 new.crystals = "present"
5770             new.known = "unknown"
5771             new.inhabited = False
5772         game.state.galaxy[w.i][w.j].planet = new
5773         game.state.planets.append(new)
5774     # Locate Romulans
5775     for i in range(game.state.nromrem):
5776         w = randplace(GALSIZE)
5777         game.state.galaxy[w.i][w.j].romulans += 1
5778     # Place the Super-Commander if needed
5779     if game.state.nscrem > 0:
5780         while True:
5781             w = randplace(GALSIZE)
5782             if welcoming(w):
5783                 break
5784         game.state.kscmdr = w
5785         game.state.galaxy[w.i][w.j].klingons += 1
5786     # Initialize times for extraneous events
5787     schedule(FSNOVA, expran(0.5 * game.intime))
5788     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5789     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5790     schedule(FBATTAK, expran(0.3*game.intime))
5791     unschedule(FCDBAS)
5792     if game.state.nscrem:
5793         schedule(FSCMOVE, 0.2777)
5794     else:
5795         unschedule(FSCMOVE)
5796     unschedule(FSCDBAS)
5797     unschedule(FDSPROB)
5798     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5799         schedule(FDISTR, expran(1.0 + game.intime))
5800     else:
5801         unschedule(FDISTR)
5802     unschedule(FENSLV)
5803     unschedule(FREPRO)
5804     # Place thing (in tournament game, we don't want one!)
5805     # New in SST2K: never place the Thing near a starbase.
5806     # This makes sense and avoids a special case in the old code.
5807     global thing
5808     if game.tourn is None:
5809         while True:
5810             thing = randplace(GALSIZE)
5811             if thing not in game.state.baseq:
5812                 break
5813     skip(2)
5814     game.state.snap = False
5815     if game.skill == SKILL_NOVICE:
5816         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5817         prout(_("a deadly Klingon invasion force. As captain of the United"))
5818         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5819         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5820         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5821         prout(_("your mission.  As you proceed you may be given more time."))
5822         skip(1)
5823         prout(_("You will have %d supporting starbases.") % (game.inbase))
5824         proutn(_("Starbase locations-  "))
5825     else:
5826         prout(_("Stardate %d.") % int(game.state.date))
5827         skip(1)
5828         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5829         prout(_("An unknown number of Romulans."))
5830         if game.state.nscrem:
5831             prout(_("And one (GULP) Super-Commander."))
5832         prout(_("%d stardates.") % int(game.intime))
5833         proutn(_("%d starbases in ") % game.inbase)
5834     for i in range(game.inbase):
5835         proutn(repr(game.state.baseq[i]))
5836         proutn("  ")
5837     skip(2)
5838     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5839     proutn(_(" Sector %s") % game.sector)
5840     skip(2)
5841     prout(_("Good Luck!"))
5842     if game.state.nscrem:
5843         prout(_("  YOU'LL NEED IT."))
5844     waitfor()
5845     clrscr()
5846     setwnd(message_window)
5847     newqad()
5848     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5849         game.shldup = True
5850     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5851         attack(torps_ok=False)
5852
5853 def choose():
5854     "Choose your game type."
5855     while True:
5856         game.tourn = game.length = 0
5857         game.thawed = False
5858         game.skill = SKILL_NONE
5859         scanner.chew()
5860 #        if not scanner.inqueue: # Can start with command line options
5861         proutn(_("Would you like a regular, tournament, or saved game? "))
5862         scanner.nexttok()
5863         if scanner.sees("tournament"):
5864             while scanner.nexttok() == "IHEOL":
5865                 proutn(_("Type in tournament number-"))
5866             if scanner.real == 0:
5867                 scanner.chew()
5868                 continue # We don't want a blank entry
5869             game.tourn = int(round(scanner.real))
5870             random.seed(scanner.real)
5871             if logfp:
5872                 logfp.write("# random.seed(%d)\n" % scanner.real)
5873             break
5874         if scanner.sees("saved") or scanner.sees("frozen"):
5875             if thaw():
5876                 continue
5877             scanner.chew()
5878             if game.passwd is None:
5879                 continue
5880             if not game.alldone:
5881                 game.thawed = True # No plaque if not finished
5882             report()
5883             waitfor()
5884             return True
5885         if scanner.sees("regular"):
5886             break
5887         proutn(_("What is \"%s\"? ") % scanner.token)
5888         scanner.chew()
5889     while game.length==0 or game.skill==SKILL_NONE:
5890         if scanner.nexttok() == "IHALPHA":
5891             if scanner.sees("short"):
5892                 game.length = 1
5893             elif scanner.sees("medium"):
5894                 game.length = 2
5895             elif scanner.sees("long"):
5896                 game.length = 4
5897             elif scanner.sees("novice"):
5898                 game.skill = SKILL_NOVICE
5899             elif scanner.sees("fair"):
5900                 game.skill = SKILL_FAIR
5901             elif scanner.sees("good"):
5902                 game.skill = SKILL_GOOD
5903             elif scanner.sees("expert"):
5904                 game.skill = SKILL_EXPERT
5905             elif scanner.sees("emeritus"):
5906                 game.skill = SKILL_EMERITUS
5907             else:
5908                 proutn(_("What is \""))
5909                 proutn(scanner.token)
5910                 prout("\"?")
5911         else:
5912             scanner.chew()
5913             if game.length==0:
5914                 proutn(_("Would you like a Short, Medium, or Long game? "))
5915             elif game.skill == SKILL_NONE:
5916                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5917     # Choose game options -- added by ESR for SST2K
5918     if scanner.nexttok() != "IHALPHA":
5919         scanner.chew()
5920         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5921         scanner.nexttok()
5922     if scanner.sees("plain"):
5923         # Approximates the UT FORTRAN version.
5924         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)
5925         game.options |= OPTION_PLAIN
5926     elif scanner.sees("almy"):
5927         # Approximates Tom Almy's version.
5928         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR)
5929         game.options |= OPTION_ALMY
5930     elif scanner.sees("fancy") or scanner.sees("\n"):
5931         pass
5932     elif len(scanner.token):
5933         proutn(_("What is \"%s\"?") % scanner.token)
5934     setpassword()
5935     if game.passwd == "debug":
5936         game.idebug = True
5937         prout("=== Debug mode enabled.")
5938     # Use parameters to generate initial values of things
5939     game.damfac = 0.5 * game.skill
5940     game.inbase = randrange(BASEMIN, BASEMAX+1)
5941     game.inplan = 0
5942     if game.options & OPTION_PLANETS:
5943         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5944     if game.options & OPTION_WORLDS:
5945         game.inplan += int(NINHAB)
5946     game.state.nromrem = game.inrom = randrange(2 * game.skill)
5947     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5948     game.state.remtime = 7.0 * game.length
5949     game.intime = game.state.remtime
5950     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5951     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5952     game.state.remres = (game.inkling+4*game.incom)*game.intime
5953     game.inresor = game.state.remres
5954     if game.inkling > 50:
5955         game.inbase += 1
5956     return False
5957
5958 def dropin(iquad=None):
5959     "Drop a feature on a random dot in the current quadrant."
5960     while True:
5961         w = randplace(QUADSIZE)
5962         if game.quad[w.i][w.j] == '.':
5963             break
5964     if iquad is not None:
5965         game.quad[w.i][w.j] = iquad
5966     return w
5967
5968 def newcnd():
5969     "Update our alert status."
5970     game.condition = "green"
5971     if game.energy < 1000.0:
5972         game.condition = "yellow"
5973     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5974         game.condition = "red"
5975     if not game.alive:
5976         game.condition="dead"
5977
5978 def newkling():
5979     "Drop new Klingon into current quadrant."
5980     return Enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
5981
5982 def sortenemies():
5983     "Sort enemies by distance so 'nearest' is meaningful."
5984     game.enemies.sort(key=lambda x: x.kdist)
5985
5986 def newqad():
5987     "Set up a new state of quadrant, for when we enter or re-enter it."
5988     game.justin = True
5989     game.iplnet = None
5990     game.neutz = game.inorbit = game.landed = False
5991     game.ientesc = game.iseenit = game.isviolreported = False
5992     # Create a blank quadrant
5993     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
5994     if game.iscate:
5995         # Attempt to escape Super-commander, so tbeam back!
5996         game.iscate = False
5997         game.ientesc = True
5998     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
5999     # cope with supernova
6000     if q.supernova:
6001         return
6002     game.klhere = q.klingons
6003     game.irhere = q.romulans
6004     # Position Starship
6005     game.quad[game.sector.i][game.sector.j] = game.ship
6006     game.enemies = []
6007     if q.klingons:
6008         # Position ordinary Klingons
6009         for _i in range(game.klhere):
6010             newkling()
6011         # If we need a commander, promote a Klingon
6012         for cmdr in game.state.kcmdr:
6013             if cmdr == game.quadrant:
6014                 e = game.enemies[game.klhere-1]
6015                 game.quad[e.location.i][e.location.j] = 'C'
6016                 e.power = randreal(950,1350) + 50.0*game.skill
6017                 break
6018         # If we need a super-commander, promote a Klingon
6019         if game.quadrant == game.state.kscmdr:
6020             e = game.enemies[0]
6021             game.quad[e.location.i][e.location.j] = 'S'
6022             e.power = randreal(1175.0,  1575.0) + 125.0*game.skill
6023             game.iscate = (game.state.remkl > 1)
6024     # Put in Romulans if needed
6025     for _i in range(q.romulans):
6026         Enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
6027     # If quadrant needs a starbase, put it in
6028     if q.starbase:
6029         game.base = dropin('B')
6030     # If quadrant needs a planet, put it in
6031     if q.planet:
6032         game.iplnet = q.planet
6033         if not q.planet.inhabited:
6034             game.plnet = dropin('P')
6035         else:
6036             game.plnet = dropin('@')
6037     # Check for condition
6038     newcnd()
6039     # Check for RNZ
6040     if game.irhere > 0 and game.klhere == 0:
6041         game.neutz = True
6042         if not damaged(DRADIO):
6043             skip(1)
6044             prout(_("LT. Uhura- \"Captain, an urgent message."))
6045             prout(_("  I'll put it on audio.\"  CLICK"))
6046             skip(1)
6047             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6048             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6049     # Put in THING if needed
6050     if thing == game.quadrant:
6051         Enemy(etype='?', loc=dropin(),
6052               power=randreal(6000,6500.0)+250.0*game.skill)
6053         if not damaged(DSRSENS):
6054             skip(1)
6055             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6056             prout(_("    Please examine your short-range scan.\""))
6057     # Decide if quadrant needs a Tholian; lighten up if skill is low
6058     if game.options & OPTION_THOLIAN:
6059         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
6060             (game.skill == SKILL_GOOD and withprob(0.05)) or \
6061             (game.skill > SKILL_GOOD and withprob(0.08)):
6062             w = Coord()
6063             while True:
6064                 w.i = withprob(0.5) * (QUADSIZE-1)
6065                 w.j = withprob(0.5) * (QUADSIZE-1)
6066                 if game.quad[w.i][w.j] == '.':
6067                     break
6068             game.tholian = Enemy(etype='T', loc=w,
6069                                  power=randrange(100, 500) + 25.0*game.skill)
6070             # Reserve unoccupied corners
6071             if game.quad[0][0]=='.':
6072                 game.quad[0][0] = 'X'
6073             if game.quad[0][QUADSIZE-1]=='.':
6074                 game.quad[0][QUADSIZE-1] = 'X'
6075             if game.quad[QUADSIZE-1][0]=='.':
6076                 game.quad[QUADSIZE-1][0] = 'X'
6077             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6078                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6079     sortenemies()
6080     # And finally the stars
6081     for _i in range(q.stars):
6082         dropin('*')
6083     # Put in a few black holes
6084     for _i in range(1, 3+1):
6085         if withprob(0.5):
6086             dropin(' ')
6087     # Take out X's in corners if Tholian present
6088     if game.tholian:
6089         if game.quad[0][0]=='X':
6090             game.quad[0][0] = '.'
6091         if game.quad[0][QUADSIZE-1]=='X':
6092             game.quad[0][QUADSIZE-1] = '.'
6093         if game.quad[QUADSIZE-1][0]=='X':
6094             game.quad[QUADSIZE-1][0] = '.'
6095         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6096             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6097
6098 def setpassword():
6099     "Set the self-destruct password."
6100     if game.options & OPTION_PLAIN:
6101         while True:
6102             scanner.chew()
6103             proutn(_("Please type in a secret password- "))
6104             scanner.nexttok()
6105             game.passwd = scanner.token
6106             if game.passwd != None:
6107                 break
6108     else:
6109         game.passwd = ""
6110         game.passwd += chr(ord('a')+randrange(26))
6111         game.passwd += chr(ord('a')+randrange(26))
6112         game.passwd += chr(ord('a')+randrange(26))
6113
6114 # Code from sst.c begins here
6115
6116 commands = [
6117     ("SRSCAN",           OPTION_TTY),
6118     ("STATUS",           OPTION_TTY),
6119     ("REQUEST",          OPTION_TTY),
6120     ("LRSCAN",           OPTION_TTY),
6121     ("PHASERS",          0),
6122     ("TORPEDO",          0),
6123     ("PHOTONS",          0),
6124     ("MOVE",             0),
6125     ("SHIELDS",          0),
6126     ("DOCK",             0),
6127     ("DAMAGES",          0),
6128     ("CHART",            0),
6129     ("IMPULSE",          0),
6130     ("REST",             0),
6131     ("WARP",             0),
6132     ("SENSORS",          OPTION_PLANETS),
6133     ("ORBIT",            OPTION_PLANETS),
6134     ("TRANSPORT",        OPTION_PLANETS),
6135     ("MINE",             OPTION_PLANETS),
6136     ("CRYSTALS",         OPTION_PLANETS),
6137     ("SHUTTLE",          OPTION_PLANETS),
6138     ("PLANETS",          OPTION_PLANETS),
6139     ("REPORT",           0),
6140     ("COMPUTER",         0),
6141     ("COMMANDS",         0),
6142     ("EMEXIT",           0),
6143     ("PROBE",            OPTION_PROBE),
6144     ("SAVE",             0),
6145     ("FREEZE",           0),        # Synonym for SAVE
6146     ("ABANDON",          0),
6147     ("DESTRUCT",         0),
6148     ("DEATHRAY",         0),
6149     ("CAPTURE",          OPTION_CAPTURE),
6150     ("CLOAK",            OPTION_CLOAK),
6151     ("DEBUG",            0),
6152     ("MAYDAY",           0),
6153     ("SOS",              0),        # Synonym for MAYDAY
6154     ("CALL",             0),        # Synonym for MAYDAY
6155     ("QUIT",             0),
6156     ("HELP",             0),
6157     ("SCORE",            0),
6158     ("",                 0),
6159 ]
6160
6161 def listCommands():
6162     "Generate a list of legal commands."
6163     prout(_("LEGAL COMMANDS ARE:"))
6164     emitted = 0
6165     for (key, opt) in commands:
6166         if not opt or (opt & game.options):
6167             proutn("%-12s " % key)
6168             emitted += 1
6169             if emitted % 5 == 4:
6170                 skip(1)
6171     skip(1)
6172
6173 def helpme():
6174     "Browse on-line help."
6175     key = scanner.nexttok()
6176     while True:
6177         if key == "IHEOL":
6178             setwnd(prompt_window)
6179             proutn(_("Help on what command? "))
6180             key = scanner.nexttok()
6181         setwnd(message_window)
6182         if key == "IHEOL":
6183             return
6184         cmds = [x[0] for x in commands]
6185         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6186             break
6187         skip(1)
6188         listCommands()
6189         key = "IHEOL"
6190         scanner.chew()
6191         skip(1)
6192     cmd = scanner.token.upper()
6193     for directory in docpath:
6194         try:
6195             fp = open(os.path.join(directory, "sst.doc"), "r")
6196             break
6197         except IOError:
6198             pass
6199     else:
6200         prout(_("Spock-  \"Captain, that information is missing from the"))
6201         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6202         proutn(_("   in these directories: %s") % ":".join(docpath))
6203         prout(".\"")
6204         # This used to continue: "You need to find SST.DOC and put
6205         # it in the current directory."
6206         return
6207     while True:
6208         linebuf = fp.readline()
6209         if linebuf == '':
6210             prout(_("Spock- \"Captain, there is no information on that command.\""))
6211             fp.close()
6212             return
6213         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6214             linebuf = linebuf[3:].strip()
6215             if cmd.upper() == linebuf:
6216                 break
6217     skip(1)
6218     prout(_("Spock- \"Captain, I've found the following information:\""))
6219     skip(1)
6220     while True:
6221         linebuf = fp.readline()
6222         if "******" in linebuf:
6223             break
6224         proutn(linebuf)
6225     fp.close()
6226
6227 def makemoves():
6228     "Command-interpretation loop."
6229     def checkviol():
6230         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6231             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6232             game.ncviol += 1
6233             game.isviolreported = True
6234     while True:         # command loop
6235         drawmaps(1)
6236         while True:        # get a command
6237             hitme = False
6238             game.optime = game.justin = False
6239             scanner.chew()
6240             setwnd(prompt_window)
6241             clrscr()
6242             proutn("COMMAND> ")
6243             if scanner.nexttok() == "IHEOL":
6244                 if game.options & OPTION_CURSES:
6245                     makechart()
6246                 continue
6247             elif scanner.token == "":
6248                 continue
6249             game.ididit = False
6250             clrscr()
6251             setwnd(message_window)
6252             clrscr()
6253             abandon_passed = False
6254             cmd = ""    # Force cmd to persist after loop
6255             opt = 0     # Force opt to persist after loop
6256             for (cmd, opt) in commands:
6257                 # commands after ABANDON cannot be abbreviated
6258                 if cmd == "ABANDON":
6259                     abandon_passed = True
6260                 if cmd == scanner.token.upper() or (not abandon_passed \
6261                         and cmd.startswith(scanner.token.upper())):
6262                     break
6263             if cmd == "":
6264                 listCommands()
6265                 continue
6266             elif opt and not (opt & game.options):
6267                 huh()
6268             else:
6269                 break
6270         if cmd == "SRSCAN":                # srscan
6271             srscan()
6272         elif cmd == "STATUS":                # status
6273             status()
6274         elif cmd == "REQUEST":                # status request
6275             request()
6276         elif cmd == "LRSCAN":                # long range scan
6277             lrscan(silent=False)
6278         elif cmd == "PHASERS":                # phasers
6279             phasers()
6280             if game.ididit:
6281                 checkviol()
6282                 hitme = True
6283         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6284             torps()
6285             if game.ididit:
6286                 checkviol()
6287                 hitme = True
6288         elif cmd == "MOVE":                # move under warp
6289             warp(wcourse=None, involuntary=False)
6290         elif cmd == "SHIELDS":                # shields
6291             doshield(shraise=False)
6292             if game.ididit:
6293                 hitme = True
6294                 game.shldchg = False
6295         elif cmd == "DOCK":                # dock at starbase
6296             dock(True)
6297             if game.ididit:
6298                 attack(torps_ok=False)
6299         elif cmd == "DAMAGES":                # damage reports
6300             damagereport()
6301         elif cmd == "CHART":                # chart
6302             makechart()
6303         elif cmd == "IMPULSE":                # impulse
6304             impulse()
6305         elif cmd == "REST":                # rest
6306             wait()
6307             if game.ididit:
6308                 hitme = True
6309         elif cmd == "WARP":                # warp
6310             setwarp()
6311         elif cmd == "SENSORS":                # sensors
6312             sensor()
6313         elif cmd == "ORBIT":                # orbit
6314             orbit()
6315             if game.ididit:
6316                 hitme = True
6317         elif cmd == "TRANSPORT":                # transport "beam"
6318             beam()
6319         elif cmd == "MINE":                # mine
6320             mine()
6321             if game.ididit:
6322                 hitme = True
6323         elif cmd == "CRYSTALS":                # crystals
6324             usecrystals()
6325             if game.ididit:
6326                 hitme = True
6327         elif cmd == "SHUTTLE":                # shuttle
6328             shuttle()
6329             if game.ididit:
6330                 hitme = True
6331         elif cmd == "PLANETS":                # Planet list
6332             survey()
6333         elif cmd == "REPORT":                # Game Report
6334             report()
6335         elif cmd == "COMPUTER":                # use COMPUTER!
6336             eta()
6337         elif cmd == "COMMANDS":
6338             listCommands()
6339         elif cmd == "EMEXIT":                # Emergency exit
6340             clrscr()                        # Hide screen
6341             freeze(True)                # forced save
6342             raise SystemExit(1)                # And quick exit
6343         elif cmd == "PROBE":
6344             probe()                        # Launch probe
6345             if game.ididit:
6346                 hitme = True
6347         elif cmd == "ABANDON":                # Abandon Ship
6348             abandon()
6349         elif cmd == "DESTRUCT":                # Self Destruct
6350             selfdestruct()
6351         elif cmd == "SAVE":                # Save Game
6352             freeze(False)
6353             clrscr()
6354             if game.skill > SKILL_GOOD:
6355                 prout(_("WARNING--Saved games produce no plaques!"))
6356         elif cmd == "DEATHRAY":                # Try a desparation measure
6357             deathray()
6358             if game.ididit:
6359                 hitme = True
6360         elif cmd == "CAPTURE":
6361             capture()
6362         elif cmd == "DEBUGCMD":                # What do we want for debug???
6363             debugme()
6364         elif cmd == "MAYDAY":                # Call for help
6365             mayday()
6366             if game.ididit:
6367                 hitme = True
6368         elif cmd == "QUIT":
6369             game.alldone = True                # quit the game
6370         elif cmd == "HELP":
6371             helpme()                        # get help
6372         elif cmd == "SCORE":
6373             score()                         # see current score
6374         while True:
6375             if game.alldone:
6376                 break                # Game has ended
6377             if game.optime != 0.0:
6378                 events()
6379                 if game.alldone:
6380                     break        # Events did us in
6381             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6382                 atover(False)
6383                 continue
6384             if hitme and not game.justin:
6385                 attack(torps_ok=True)
6386                 if game.alldone:
6387                     break
6388                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6389                     atover(False)
6390                     hitme = True
6391                     continue
6392             break
6393         if game.alldone:
6394             break
6395     if game.idebug:
6396         prout("=== Ending")
6397
6398 def cramen(ch):
6399     "Emit the name of an enemy or feature."
6400     if   ch == 'R': s = _("Romulan")
6401     elif ch == 'K': s = _("Klingon")
6402     elif ch == 'C': s = _("Commander")
6403     elif ch == 'S': s = _("Super-commander")
6404     elif ch == '*': s = _("Star")
6405     elif ch == 'P': s = _("Planet")
6406     elif ch == 'B': s = _("Starbase")
6407     elif ch == ' ': s = _("Black hole")
6408     elif ch == 'T': s = _("Tholian")
6409     elif ch == '#': s = _("Tholian web")
6410     elif ch == '?': s = _("Stranger")
6411     elif ch == '@': s = _("Inhabited World")
6412     else: s = "Unknown??"
6413     return s
6414
6415 def crmena(loud, enemy, loctype, w):
6416     "Emit the name of an enemy and his location."
6417     buf = ""
6418     if loud:
6419         buf += "***"
6420     buf += cramen(enemy) + _(" at ")
6421     if loctype == "quadrant":
6422         buf += _("Quadrant ")
6423     elif loctype == "sector":
6424         buf += _("Sector ")
6425     return buf + repr(w)
6426
6427 def crmshp():
6428     "Emit our ship name."
6429     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6430
6431 def stars():
6432     "Emit a line of stars"
6433     prouts("******************************************************")
6434     skip(1)
6435
6436 def expran(avrage):
6437     return -avrage*math.log(1e-7 + randreal())
6438
6439 def randplace(size):
6440     "Choose a random location."
6441     w = Coord()
6442     w.i = randrange(size)
6443     w.j = randrange(size)
6444     return w
6445
6446 class sstscanner:
6447     def __init__(self):
6448         self.type = None
6449         self.token = None
6450         self.real = 0.0
6451         self.inqueue = []
6452     def nexttok(self):
6453         # Get a token from the user
6454         self.real = 0.0
6455         self.token = ''
6456         # Fill the token quue if nothing here
6457         while not self.inqueue:
6458             sline = cgetline()
6459             if curwnd==prompt_window:
6460                 clrscr()
6461                 setwnd(message_window)
6462                 clrscr()
6463             if sline == '':
6464                 return None
6465             if not sline:
6466                 continue
6467             else:
6468                 self.inqueue = sline.lstrip().split() + ["\n"]
6469         # From here on in it's all looking at the queue
6470         self.token = self.inqueue.pop(0)
6471         if self.token == "\n":
6472             self.type = "IHEOL"
6473             return "IHEOL"
6474         try:
6475             self.real = float(self.token)
6476             self.type = "IHREAL"
6477             return "IHREAL"
6478         except ValueError:
6479             pass
6480         # Treat as alpha
6481         self.token = self.token.lower()
6482         self.type = "IHALPHA"
6483         self.real = None
6484         return "IHALPHA"
6485     def append(self, tok):
6486         self.inqueue.append(tok)
6487     def push(self, tok):
6488         self.inqueue.insert(0, tok)
6489     def waiting(self):
6490         return self.inqueue
6491     def chew(self):
6492         # Demand input for next scan
6493         self.inqueue = []
6494         self.real = self.token = None
6495     def sees(self, s):
6496         # compares s to item and returns true if it matches to the length of s
6497         return s.startswith(self.token)
6498     def int(self):
6499         # Round token value to nearest integer
6500         return int(round(self.real))
6501     def getcoord(self):
6502         s = Coord()
6503         self.nexttok()
6504         if self.type != "IHREAL":
6505             huh()
6506             return None
6507         s.i = self.int()-1
6508         self.nexttok()
6509         if self.type != "IHREAL":
6510             huh()
6511             return None
6512         s.j = self.int()-1
6513         return s
6514     def __repr__(self):
6515         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6516
6517 def ja():
6518     "Yes-or-no confirmation."
6519     scanner.chew()
6520     while True:
6521         scanner.nexttok()
6522         if scanner.token == 'y':
6523             return True
6524         if scanner.token == 'n':
6525             return False
6526         scanner.chew()
6527         proutn(_("Please answer with \"y\" or \"n\": "))
6528
6529 def huh():
6530     "Complain about unparseable input."
6531     scanner.chew()
6532     skip(1)
6533     prout(_("Beg your pardon, Captain?"))
6534
6535 def debugme():
6536     "Access to the internals for debugging."
6537     proutn("Reset levels? ")
6538     if ja():
6539         if game.energy < game.inenrg:
6540             game.energy = game.inenrg
6541         game.shield = game.inshld
6542         game.torps = game.intorps
6543         game.lsupres = game.inlsr
6544     proutn("Reset damage? ")
6545     if ja():
6546         for i in range(NDEVICES):
6547             if game.damage[i] > 0.0:
6548                 game.damage[i] = 0.0
6549     proutn("Toggle debug flag? ")
6550     if ja():
6551         game.idebug = not game.idebug
6552         if game.idebug:
6553             prout("Debug output ON")
6554         else:
6555             prout("Debug output OFF")
6556     proutn("Cause selective damage? ")
6557     if ja():
6558         for i in range(NDEVICES):
6559             proutn("Kill %s?" % device[i])
6560             scanner.chew()
6561             key = scanner.nexttok()
6562             if key == "IHALPHA" and scanner.sees("y"):
6563                 game.damage[i] = 10.0
6564     proutn("Examine/change events? ")
6565     if ja():
6566         ev = Event()
6567         w = Coord()
6568         legends = {
6569             FSNOVA:  "Supernova       ",
6570             FTBEAM:  "T Beam          ",
6571             FSNAP:   "Snapshot        ",
6572             FBATTAK: "Base Attack     ",
6573             FCDBAS:  "Base Destroy    ",
6574             FSCMOVE: "SC Move         ",
6575             FSCDBAS: "SC Base Destroy ",
6576             FDSPROB: "Probe Move      ",
6577             FDISTR:  "Distress Call   ",
6578             FENSLV:  "Enslavement     ",
6579             FREPRO:  "Klingon Build   ",
6580         }
6581         for i in range(1, NEVENTS):
6582             proutn(legends[i])
6583             if is_scheduled(i):
6584                 proutn("%.2f" % (scheduled(i)-game.state.date))
6585                 if i == FENSLV or i == FREPRO:
6586                     ev = findevent(i)
6587                     proutn(" in %s" % ev.quadrant)
6588             else:
6589                 proutn("never")
6590             proutn("? ")
6591             scanner.chew()
6592             key = scanner.nexttok()
6593             if key == 'n':
6594                 unschedule(i)
6595                 scanner.chew()
6596             elif key == "IHREAL":
6597                 ev = schedule(i, scanner.real)
6598                 if i == FENSLV or i == FREPRO:
6599                     scanner.chew()
6600                     proutn("In quadrant- ")
6601                     key = scanner.nexttok()
6602                     # "IHEOL" says to leave coordinates as they are
6603                     if key != "IHEOL":
6604                         if key != "IHREAL":
6605                             prout("Event %d canceled, no x coordinate." % (i))
6606                             unschedule(i)
6607                             continue
6608                         w.i = int(round(scanner.real))
6609                         key = scanner.nexttok()
6610                         if key != "IHREAL":
6611                             prout("Event %d canceled, no y coordinate." % (i))
6612                             unschedule(i)
6613                             continue
6614                         w.j = int(round(scanner.real))
6615                         ev.quadrant = w
6616         scanner.chew()
6617     proutn("Induce supernova here? ")
6618     if ja():
6619         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6620         atover(True)
6621
6622 if __name__ == '__main__':
6623     try:
6624         #global line, thing, game
6625         game = None
6626         thing = Thingy()
6627         game = Gamestate()
6628         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6629         if os.getenv("TERM"):
6630             game.options |= OPTION_CURSES
6631         else:
6632             game.options |= OPTION_TTY
6633         seed = int(time.time())
6634         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:txV")
6635         replay = False
6636         for (switch, val) in options:
6637             if switch == '-r':
6638                 try:
6639                     replayfp = open(val, "r")
6640                 except IOError:
6641                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6642                     raise SystemExit(1)
6643                 try:
6644                     line = replayfp.readline().strip()
6645                     (leader, __, seed) = line.split()
6646                     seed = eval(seed)
6647                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6648                     line = replayfp.readline().strip()
6649                     arguments += line.split()[2:]
6650                     replay = True
6651                 except ValueError:
6652                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6653                     raise SystemExit(1)
6654                 game.options |= OPTION_TTY
6655                 game.options &=~ OPTION_CURSES
6656             elif switch == '-s':
6657                 seed = int(val)
6658             elif switch == '-t':
6659                 game.options |= OPTION_TTY
6660                 game.options &=~ OPTION_CURSES
6661             elif switch == '-x':
6662                 game.idebug = True
6663             elif switch == '-V':
6664                 print("SST2K", version)
6665                 raise SystemExit(0)
6666             else:
6667                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6668                 raise SystemExit(1)
6669         # where to save the input in case of bugs
6670         if "TMPDIR" in os.environ:
6671             tmpdir = os.environ['TMPDIR']
6672         else:
6673             tmpdir = "/tmp"
6674         try:
6675             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6676         except IOError:
6677             sys.stderr.write("sst: warning, can't open logfile\n")
6678             sys.exit(1)
6679         if logfp:
6680             logfp.write("# seed %s\n" % seed)
6681             logfp.write("# options %s\n" % " ".join(arguments))
6682             logfp.write("# SST2K version %s\n" % version)
6683             logfp.write("# recorded by %s@%s on %s\n" % \
6684                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6685         random.seed(seed)
6686         scanner = sstscanner()
6687         for arg in arguments:
6688             scanner.append(arg)
6689         try:
6690             iostart()
6691             while True: # Play a game
6692                 setwnd(fullscreen_window)
6693                 clrscr()
6694                 prelim()
6695                 setup()
6696                 if game.alldone:
6697                     score()
6698                     game.alldone = False
6699                 else:
6700                     makemoves()
6701                 if replay:
6702                     break
6703                 skip(1)
6704                 stars()
6705                 skip(1)
6706                 if game.tourn and game.alldone:
6707                     proutn(_("Do you want your score recorded?"))
6708                     if ja():
6709                         scanner.chew()
6710                         scanner.push("\n")
6711                         freeze(False)
6712                 scanner.chew()
6713                 proutn(_("Do you want to play again? "))
6714                 if not ja():
6715                     break
6716             skip(1)
6717             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6718         finally:
6719             ioend()
6720         raise SystemExit(0)
6721     except KeyboardInterrupt:
6722         if logfp:
6723             logfp.close()
6724         print("")
6725
6726 # End.