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