b25eeaf41d9d035c618c03000aab9394df33cf8c
[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 etype == '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 etype ==  'K':
1618             game.state.remkl -= 1
1619         elif etype ==  '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 not kk < len(game.enemies):
1805             # Should never happen.
1806             break
1807         if wham == 0:
1808             continue
1809         dustfac = randreal(0.9, 1.0)
1810         hit = wham*math.pow(dustfac, game.enemies[kk].kdist)
1811         kpini = game.enemies[kk].power
1812         kp = math.fabs(kpini)
1813         if PHASEFAC*hit < kp:
1814             kp = PHASEFAC*hit
1815         if game.enemies[kk].power < 0:
1816             game.enemies[kk].power -= -kp
1817         else:
1818             game.enemies[kk].power -= kp
1819         kpow = game.enemies[kk].power
1820         w = game.enemies[kk].location
1821         if hit > 0.005:
1822             if not damaged(DSRSENS):
1823                 boom(w)
1824             proutn(_("%d unit hit on ") % int(hit))
1825         else:
1826             proutn(_("Very small hit on "))
1827         ienm = game.quad[w.i][w.j]
1828         if ienm == '?':
1829             thing.angry()
1830         proutn(crmena(False, ienm, "sector", w))
1831         skip(1)
1832         if kpow == 0:
1833             deadkl(w, ienm, w)
1834             if game.unwon()==0:
1835                 finish(FWON)
1836             if game.alldone:
1837                 return
1838             continue
1839         else: # decide whether or not to emasculate klingon
1840             if kpow > 0 and withprob(0.9) and kpow <= randreal(0.4, 0.8)*kpini:
1841                 prout(_("***Mr. Spock-  \"Captain, the vessel at Sector %s")%w)
1842                 prout(_("   has just lost its firepower.\""))
1843                 game.enemies[kk].power = -kpow
1844         kk += 1
1845     return
1846
1847 def phasers():
1848     "Fire phasers at bad guys."
1849     hits = []
1850     kz = 0
1851     k = 1
1852     irec = 0 # Cheating inhibitor
1853     ifast = False
1854     no = False
1855     itarg = True
1856     msgflag = True
1857     rpow = 0.0
1858     automode = "NOTSET"
1859     key = ""
1860     skip(1)
1861     # SR sensors and Computer are needed for automode
1862     if damaged(DSRSENS) or damaged(DCOMPTR):
1863         itarg = False
1864     if game.condition == "docked":
1865         prout(_("Phasers can't be fired through base shields."))
1866         scanner.chew()
1867         return
1868     if damaged(DPHASER):
1869         prout(_("Phaser control damaged."))
1870         scanner.chew()
1871         return
1872     if game.shldup:
1873         if damaged(DSHCTRL):
1874             prout(_("High speed shield control damaged."))
1875             scanner.chew()
1876             return
1877         if game.energy <= 200.0:
1878             prout(_("Insufficient energy to activate high-speed shield control."))
1879             scanner.chew()
1880             return
1881         prout(_("Weapons Officer Sulu-  \"High-speed shield control enabled, sir.\""))
1882         ifast = True
1883     # Original code so convoluted, I re-did it all
1884     # (That was Tom Almy talking about the C code, I think -- ESR)
1885     while automode == "NOTSET":
1886         key = scanner.nexttok()
1887         if key == "IHALPHA":
1888             if scanner.sees("manual"):
1889                 if len(game.enemies)==0:
1890                     prout(_("There is no enemy present to select."))
1891                     scanner.chew()
1892                     key = "IHEOL"
1893                     automode = "AUTOMATIC"
1894                 else:
1895                     automode = "MANUAL"
1896                     key = scanner.nexttok()
1897             elif scanner.sees("automatic"):
1898                 if (not itarg) and len(game.enemies) != 0:
1899                     automode = "FORCEMAN"
1900                 else:
1901                     if len(game.enemies)==0:
1902                         prout(_("Energy will be expended into space."))
1903                     automode = "AUTOMATIC"
1904                     key = scanner.nexttok()
1905             elif scanner.sees("no"):
1906                 no = True
1907             else:
1908                 huh()
1909                 return
1910         elif key == "IHREAL":
1911             if len(game.enemies)==0:
1912                 prout(_("Energy will be expended into space."))
1913                 automode = "AUTOMATIC"
1914             elif not itarg:
1915                 automode = "FORCEMAN"
1916             else:
1917                 automode = "AUTOMATIC"
1918         else:
1919             # "IHEOL"
1920             if len(game.enemies)==0:
1921                 prout(_("Energy will be expended into space."))
1922                 automode = "AUTOMATIC"
1923             elif not itarg:
1924                 automode = "FORCEMAN"
1925             else:
1926                 proutn(_("Manual or automatic? "))
1927                 scanner.chew()
1928     avail = game.energy
1929     if ifast:
1930         avail -= 200.0
1931     if automode == "AUTOMATIC":
1932         if key == "IHALPHA" and scanner.sees("no"):
1933             no = True
1934             key = scanner.nexttok()
1935         if key != "IHREAL" and len(game.enemies) != 0:
1936             prout(_("Phasers locked on target. Energy available: %.2f")%avail)
1937         irec = 0
1938         while True:
1939             scanner.chew()
1940             if not kz:
1941                 for i in range(len(game.enemies)):
1942                     irec += math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))*randreal(1.01, 1.06) + 1.0
1943             kz = 1
1944             proutn(_("%d units required. ") % irec)
1945             scanner.chew()
1946             proutn(_("Units to fire= "))
1947             key = scanner.nexttok()
1948             if key != "IHREAL":
1949                 return
1950             rpow = scanner.real
1951             if rpow > avail:
1952                 proutn(_("Energy available= %.2f") % avail)
1953                 skip(1)
1954                 key = "IHEOL"
1955             if not rpow > avail:
1956                 break
1957         if rpow <= 0:
1958             # chicken out
1959             scanner.chew()
1960             return
1961         key = scanner.nexttok()
1962         if key == "IHALPHA" and scanner.sees("no"):
1963             no = True
1964         if ifast:
1965             game.energy -= 200 # Go and do it!
1966             if checkshctrl(rpow):
1967                 return
1968         scanner.chew()
1969         game.energy -= rpow
1970         extra = rpow
1971         if len(game.enemies):
1972             extra = 0.0
1973             powrem = rpow
1974             for i in range(len(game.enemies)):
1975                 hits.append(0.0)
1976                 if powrem <= 0:
1977                     continue
1978                 hits[i] = math.fabs(game.enemies[i].power)/(PHASEFAC*math.pow(0.90, game.enemies[i].kdist))
1979                 over = randreal(1.01, 1.06) * hits[i]
1980                 temp = powrem
1981                 powrem -= hits[i] + over
1982                 if powrem <= 0 and temp < hits[i]:
1983                     hits[i] = temp
1984                 if powrem <= 0:
1985                     over = 0.0
1986                 extra += over
1987             if powrem > 0.0:
1988                 extra += powrem
1989             hittem(hits)
1990             game.ididit = True
1991         if extra > 0 and not game.alldone:
1992             if game.tholian:
1993                 proutn(_("*** Tholian web absorbs "))
1994                 if len(game.enemies)>0:
1995                     proutn(_("excess "))
1996                 prout(_("phaser energy."))
1997             else:
1998                 prout(_("%d expended on empty space.") % int(extra))
1999     elif automode == "FORCEMAN":
2000         scanner.chew()
2001         key = "IHEOL"
2002         if damaged(DCOMPTR):
2003             prout(_("Battle computer damaged, manual fire only."))
2004         else:
2005             skip(1)
2006             prouts(_("---WORKING---"))
2007             skip(1)
2008             prout(_("Short-range-sensors-damaged"))
2009             prout(_("Insufficient-data-for-automatic-phaser-fire"))
2010             prout(_("Manual-fire-must-be-used"))
2011             skip(1)
2012     elif automode == "MANUAL":
2013         rpow = 0.0
2014         for k in range(len(game.enemies)):
2015             aim = game.enemies[k].location
2016             ienm = game.quad[aim.i][aim.j]
2017             if msgflag:
2018                 proutn(_("Energy available= %.2f") % (avail-0.006))
2019                 skip(1)
2020                 msgflag = False
2021                 rpow = 0.0
2022             if damaged(DSRSENS) and \
2023                not game.sector.distance(aim)<2**0.5 and ienm in ('C', 'S'):
2024                 prout(cramen(ienm) + _(" can't be located without short range scan."))
2025                 scanner.chew()
2026                 key = "IHEOL"
2027                 hits[k] = 0 # prevent overflow -- thanks to Alexei Voitenko
2028                 k += 1
2029                 continue
2030             if key == "IHEOL":
2031                 scanner.chew()
2032                 if itarg and k > kz:
2033                     irec = (abs(game.enemies[k].power)/(PHASEFAC*math.pow(0.9, game.enemies[k].kdist))) *        randreal(1.01, 1.06) + 1.0
2034                 kz = k
2035                 proutn("(")
2036                 if not damaged(DCOMPTR):
2037                     proutn("%d" % irec)
2038                 else:
2039                     proutn("??")
2040                 proutn(")  ")
2041                 proutn(_("units to fire at %s-  ") % crmena(False, ienm, "sector", aim))
2042                 key = scanner.nexttok()
2043             if key == "IHALPHA" and scanner.sees("no"):
2044                 no = True
2045                 key = scanner.nexttok()
2046                 continue
2047             if key == "IHALPHA":
2048                 huh()
2049                 return
2050             if key == "IHEOL":
2051                 if k == 1: # Let me say I'm baffled by this
2052                     msgflag = True
2053                 continue
2054             if scanner.real < 0:
2055                 # abort out
2056                 scanner.chew()
2057                 return
2058             hits[k] = scanner.real
2059             rpow += scanner.real
2060             # If total requested is too much, inform and start over
2061             if rpow > avail:
2062                 prout(_("Available energy exceeded -- try again."))
2063                 scanner.chew()
2064                 return
2065             key = scanner.nexttok() # scan for next value
2066             k += 1
2067         if rpow == 0.0:
2068             # zero energy -- abort
2069             scanner.chew()
2070             return
2071         if key == "IHALPHA" and scanner.sees("no"):
2072             no = True
2073         game.energy -= rpow
2074         scanner.chew()
2075         if ifast:
2076             game.energy -= 200.0
2077             if checkshctrl(rpow):
2078                 return
2079         hittem(hits)
2080         game.ididit = True
2081      # Say shield raised or malfunction, if necessary
2082     if game.alldone:
2083         return
2084     if ifast:
2085         skip(1)
2086         if no == 0:
2087             if withprob(0.01):
2088                 prout(_("Sulu-  \"Sir, the high-speed shield control has malfunctioned . . ."))
2089                 prouts(_("         CLICK   CLICK   POP  . . ."))
2090                 prout(_(" No response, sir!"))
2091                 game.shldup = False
2092             else:
2093                 prout(_("Shields raised."))
2094         else:
2095             game.shldup = False
2096     overheat(rpow)
2097
2098
2099 def capture():
2100     game.ididit = False # Nothing if we fail
2101     game.optime = 0.0;
2102
2103     # Make sure there is room in the brig */
2104     if game.brigfree == 0:
2105         prout(_("Security reports the brig is already full."))
2106         return;
2107
2108     if damaged(DRADIO):
2109         prout(_("Uhura- \"We have no subspace radio communication, sir.\""))
2110         return
2111
2112     if damaged(DTRANSP):
2113         prout(_("Scotty- \"Transporter damaged, sir.\""))
2114         return
2115
2116     # find out if there are any at all
2117     if game.klhere < 1:
2118         prout(_("Uhura- \"Getting no response, sir.\""))
2119         return
2120
2121     # if there is more than one Klingon, find out which one */
2122     #   Cruddy, just takes one at random.  Should ask the captain.
2123     #   Nah, just select the weakest one since it is most likely to
2124     #   surrender (Tom Almy mod)
2125     klingons = [e for e in game.enemies if e.type == 'K']
2126     weakest = sorted(klingons, key=lambda e: e.power)[0]
2127     game.optime = 0.05          # This action will take some time
2128     game.ididit = True #  So any others can strike back
2129
2130     # check out that Klingon
2131     # The algorithm isn't that great and could use some more
2132     # intelligent design
2133     # x = 300 + 25*skill;
2134     x = game.energy / (weakest.power * len(klingons))
2135     #prout(_("Stats: energy = %s, kpower = %s, klingons = %s")
2136     #      % (game.energy, weakest.power, len(klingons))) 
2137     x *= 2.5  # would originally have been equivalent of 1.4,
2138                # but we want command to work more often, more humanely */
2139     #prout(_("Prob = %.4f" % x))
2140     #   x = 100; // For testing, of course!
2141     if x < randreal(100):
2142         # guess what, he surrendered!!! */
2143         prout(_("Klingon captain at %s surrenders.") % weakest.location)
2144         i = randreal(200)
2145         if i > 0:
2146             prout(_("%d Klingons commit suicide rather than be taken captive.") % (200 - i))
2147         if i > game.brigfree:
2148             prout(_("%d Klingons die because there is no room for them in the brig.") % (i-brigfree))
2149             i = game.brigfree
2150         game.brigfree -= i
2151         prout(_("%d captives taken") % i)
2152         deadkl(weakest.location, weakest.type, game.sector)
2153         if game.unwon()<=0:
2154             finish(FWON)
2155         return
2156
2157         # big surprise, he refuses to surrender */
2158     prout(_("Fat chance, captain!"))
2159
2160 # Code from events.c begins here.
2161
2162 # This isn't a real event queue a la BSD Trek yet -- you can only have one
2163 # event of each type active at any given time.  Mostly these means we can
2164 # only have one FDISTR/FENSLV/FREPRO sequence going at any given time
2165 # BSD Trek, from which we swiped the idea, can have up to 5.
2166
2167 def unschedule(evtype):
2168     "Remove an event from the schedule."
2169     game.future[evtype].date = FOREVER
2170     return game.future[evtype]
2171
2172 def is_scheduled(evtype):
2173     "Is an event of specified type scheduled."
2174     return game.future[evtype].date != FOREVER
2175
2176 def scheduled(evtype):
2177     "When will this event happen?"
2178     return game.future[evtype].date
2179
2180 def schedule(evtype, offset):
2181     "Schedule an event of specified type."
2182     game.future[evtype].date = game.state.date + offset
2183     return game.future[evtype]
2184
2185 def postpone(evtype, offset):
2186     "Postpone a scheduled event."
2187     game.future[evtype].date += offset
2188
2189 def cancelrest():
2190     "Rest period is interrupted by event."
2191     if game.resting:
2192         skip(1)
2193         proutn(_("Mr. Spock-  \"Captain, shall we cancel the rest period?\""))
2194         if ja():
2195             game.resting = False
2196             game.optime = 0.0
2197             return True
2198     return False
2199
2200 def events():
2201     "Run through the event queue looking for things to do."
2202     i = 0
2203     fintim = game.state.date + game.optime
2204     yank = 0
2205     ictbeam = False
2206     istract = False
2207     w = Coord()
2208     hold = Coord()
2209     ev = Event()
2210     ev2 = Event()
2211
2212     def tractorbeam(yank):
2213         "Tractor-beaming cases merge here."
2214         announce()
2215         game.optime = (10.0/(7.5*7.5))*yank # 7.5 is yank rate (warp 7.5)
2216         skip(1)
2217         prout("***" + crmshp() + _(" caught in long range tractor beam--"))
2218         # If Kirk & Co. screwing around on planet, handle
2219         atover(True) # atover(true) is Grab
2220         if game.alldone:
2221             return
2222         if game.icraft: # Caught in Galileo?
2223             finish(FSTRACTOR)
2224             return
2225         # Check to see if shuttle is aboard
2226         if game.iscraft == "offship":
2227             skip(1)
2228             if withprob(0.5):
2229                 prout(_("Galileo, left on the planet surface, is captured"))
2230                 prout(_("by aliens and made into a flying McDonald's."))
2231                 game.damage[DSHUTTL] = -10
2232                 game.iscraft = "removed"
2233             else:
2234                 prout(_("Galileo, left on the planet surface, is well hidden."))
2235         if evcode == FSPY:
2236             game.quadrant = game.state.kscmdr
2237         else:
2238             game.quadrant = game.state.kcmdr[i]
2239         game.sector = randplace(QUADSIZE)
2240         prout(crmshp() + _(" is pulled to Quadrant %s, Sector %s") \
2241                % (game.quadrant, game.sector))
2242         if game.resting:
2243             prout(_("(Remainder of rest/repair period cancelled.)"))
2244             game.resting = False
2245         if not game.shldup:
2246             if not damaged(DSHIELD) and game.shield > 0:
2247                 doshield(shraise=True) # raise shields
2248                 game.shldchg = False
2249             else:
2250                 prout(_("(Shields not currently useable.)"))
2251         newqad()
2252         # Adjust finish time to time of tractor beaming?
2253         # fintim = game.state.date+game.optime
2254         attack(torps_ok=False)
2255         if not game.state.kcmdr:
2256             unschedule(FTBEAM)
2257         else:
2258             schedule(FTBEAM, game.optime+expran(1.5*game.intime/len(game.state.kcmdr)))
2259
2260     def destroybase():
2261         "Code merges here for any commander destroying a starbase."
2262         # Not perfect, but will have to do
2263         # Handle case where base is in same quadrant as starship
2264         if game.battle == game.quadrant:
2265             game.state.chart[game.battle.i][game.battle.j].starbase = False
2266             game.quad[game.base.i][game.base.j] = '.'
2267             game.base.invalidate()
2268             newcnd()
2269             skip(1)
2270             prout(_("Spock-  \"Captain, I believe the starbase has been destroyed.\""))
2271         elif game.state.baseq and communicating():
2272             # Get word via subspace radio
2273             announce()
2274             skip(1)
2275             prout(_("Lt. Uhura-  \"Captain, Starfleet Command reports that"))
2276             proutn(_("   the starbase in Quadrant %s has been destroyed by") % game.battle)
2277             if game.isatb == 2:
2278                 prout(_("the Klingon Super-Commander"))
2279             else:
2280                 prout(_("a Klingon Commander"))
2281             game.state.chart[game.battle.i][game.battle.j].starbase = False
2282         # Remove Starbase from galaxy
2283         game.state.galaxy[game.battle.i][game.battle.j].starbase = False
2284         game.state.baseq = [x for x in game.state.baseq if x != game.battle]
2285         if game.isatb == 2:
2286             # reinstate a commander's base attack
2287             game.battle = hold
2288             game.isatb = 0
2289         else:
2290             game.battle.invalidate()
2291     if game.idebug:
2292         prout("=== EVENTS from %.2f to %.2f:" % (game.state.date, fintim))
2293         for i in range(1, NEVENTS):
2294             if   i == FSNOVA:  proutn("=== Supernova       ")
2295             elif i == FTBEAM:  proutn("=== T Beam          ")
2296             elif i == FSNAP:   proutn("=== Snapshot        ")
2297             elif i == FBATTAK: proutn("=== Base Attack     ")
2298             elif i == FCDBAS:  proutn("=== Base Destroy    ")
2299             elif i == FSCMOVE: proutn("=== SC Move         ")
2300             elif i == FSCDBAS: proutn("=== SC Base Destroy ")
2301             elif i == FDSPROB: proutn("=== Probe Move      ")
2302             elif i == FDISTR:  proutn("=== Distress Call   ")
2303             elif i == FENSLV:  proutn("=== Enslavement     ")
2304             elif i == FREPRO:  proutn("=== Klingon Build   ")
2305             if is_scheduled(i):
2306                 prout("%.2f" % (scheduled(i)))
2307             else:
2308                 prout("never")
2309     radio_was_broken = damaged(DRADIO)
2310     hold.i = hold.j = 0
2311     while True:
2312         # Select earliest extraneous event, evcode==0 if no events
2313         evcode = FSPY
2314         if game.alldone:
2315             return
2316         datemin = fintim
2317         for l in range(1, NEVENTS):
2318             if game.future[l].date < datemin:
2319                 evcode = l
2320                 if game.idebug:
2321                     prout("== Event %d fires" % evcode)
2322                 datemin = game.future[l].date
2323         xtime = datemin-game.state.date
2324         if game.iscloaked:
2325             game.energy -= xtime*500.0
2326             if game.energy <= 0:
2327                 finish(FNRG)
2328                 return
2329         game.state.date = datemin
2330         # Decrement Federation resources and recompute remaining time
2331         game.state.remres -= (game.state.remkl+4*len(game.state.kcmdr))*xtime
2332         game.recompute()
2333         if game.state.remtime <= 0:
2334             finish(FDEPLETE)
2335             return
2336         # Any crew left alive?
2337         if game.state.crew <= 0:
2338             finish(FCREW)
2339             return
2340         # Is life support adequate?
2341         if damaged(DLIFSUP) and game.condition != "docked":
2342             if game.lsupres < xtime and game.damage[DLIFSUP] > game.lsupres:
2343                 finish(FLIFESUP)
2344                 return
2345             game.lsupres -= xtime
2346             if game.damage[DLIFSUP] <= xtime:
2347                 game.lsupres = game.inlsr
2348         # Fix devices
2349         repair = xtime
2350         if game.condition == "docked":
2351             repair /= DOCKFAC
2352         # Don't fix Deathray here
2353         for l in range(NDEVICES):
2354             if game.damage[l] > 0.0 and l != DDRAY:
2355                 if game.damage[l]-repair > 0.0:
2356                     game.damage[l] -= repair
2357                 else:
2358                     game.damage[l] = 0.0
2359         # If radio repaired, update star chart and attack reports
2360         if radio_was_broken and not damaged(DRADIO):
2361             prout(_("Lt. Uhura- \"Captain, the sub-space radio is working and"))
2362             prout(_("   surveillance reports are coming in."))
2363             skip(1)
2364             if not game.iseenit:
2365                 attackreport(False)
2366                 game.iseenit = True
2367             rechart()
2368             prout(_("   The star chart is now up to date.\""))
2369             skip(1)
2370         # Cause extraneous event EVCODE to occur
2371         game.optime -= xtime
2372         if evcode == FSNOVA: # Supernova
2373             announce()
2374             supernova(None)
2375             schedule(FSNOVA, expran(0.5*game.intime))
2376             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2377                 return
2378         elif evcode == FSPY: # Check with spy to see if SC should tractor beam
2379             if game.state.nscrem == 0 or game.iscloaked or \
2380                 ictbeam or istract or \
2381                 game.condition == "docked" or game.isatb == 1 or game.iscate:
2382                 return
2383             if game.ientesc or \
2384                 (game.energy<2000 and game.torps<4 and game.shield < 1250) or \
2385                 (damaged(DPHASER) and (damaged(DPHOTON) or game.torps<4)) or \
2386                 (damaged(DSHIELD) and \
2387                  (game.energy < 2500 or damaged(DPHASER)) and \
2388                  (game.torps < 5 or damaged(DPHOTON))):
2389                 # Tractor-beam her!
2390                 istract = ictbeam = True
2391                 tractorbeam((game.state.kscmdr-game.quadrant).distance())
2392             else:
2393                 return
2394         elif evcode == FTBEAM: # Tractor beam
2395             if not game.state.kcmdr:
2396                 unschedule(FTBEAM)
2397                 continue
2398             i = randrange(len(game.state.kcmdr))
2399             yank = (game.state.kcmdr[i]-game.quadrant).distance()
2400             if istract or game.condition == "docked" or game.iscloaked or yank == 0:
2401                 # Drats! Have to reschedule
2402                 schedule(FTBEAM,
2403                          game.optime + expran(1.5*game.intime/len(game.state.kcmdr)))
2404                 continue
2405             ictbeam = True
2406             tractorbeam(yank)
2407         elif evcode == FSNAP: # Snapshot of the universe (for time warp)
2408             game.snapsht = copy.deepcopy(game.state)
2409             game.state.snap = True
2410             schedule(FSNAP, expran(0.5 * game.intime))
2411         elif evcode == FBATTAK: # Commander attacks starbase
2412             if not game.state.kcmdr or not game.state.baseq:
2413                 # no can do
2414                 unschedule(FBATTAK)
2415                 unschedule(FCDBAS)
2416                 continue
2417             ibq = None  # Force battle location to persist past loop
2418             try:
2419                 for ibq in game.state.baseq:
2420                     for cmdr in game.state.kcmdr:
2421                         if ibq == cmdr and ibq != game.quadrant and ibq != game.state.kscmdr:
2422                             raise JumpOut
2423                 # no match found -- try later
2424                 schedule(FBATTAK, expran(0.3*game.intime))
2425                 unschedule(FCDBAS)
2426                 continue
2427             except JumpOut:
2428                 pass
2429             # commander + starbase combination found -- launch attack
2430             game.battle = ibq
2431             schedule(FCDBAS, randreal(1.0, 4.0))
2432             if game.isatb: # extra time if SC already attacking
2433                 postpone(FCDBAS, scheduled(FSCDBAS)-game.state.date)
2434             game.future[FBATTAK].date = game.future[FCDBAS].date + expran(0.3*game.intime)
2435             game.iseenit = False
2436             if not communicating():
2437                 continue # No warning :-(
2438             game.iseenit = True
2439             announce()
2440             skip(1)
2441             prout(_("Lt. Uhura-  \"Captain, the starbase in Quadrant %s") % game.battle)
2442             prout(_("   reports that it is under attack and that it can"))
2443             prout(_("   hold out only until stardate %d.\"") % (int(scheduled(FCDBAS))))
2444             if cancelrest():
2445                 return
2446         elif evcode == FSCDBAS: # Supercommander destroys base
2447             unschedule(FSCDBAS)
2448             game.isatb = 2
2449             if not game.state.galaxy[game.state.kscmdr.i][game.state.kscmdr.j].starbase:
2450                 continue # WAS RETURN!
2451             hold = game.battle
2452             game.battle = game.state.kscmdr
2453             destroybase()
2454         elif evcode == FCDBAS: # Commander succeeds in destroying base
2455             if evcode == FCDBAS:
2456                 unschedule(FCDBAS)
2457                 if not game.state.baseq() \
2458                        or not game.state.galaxy[game.battle.i][game.battle.j].starbase:
2459                     game.battle.invalidate()
2460                     continue
2461                 # find the lucky pair
2462                 for cmdr in game.state.kcmdr:
2463                     if cmdr == game.battle:
2464                         break
2465                 else:
2466                     # No action to take after all
2467                     continue
2468             destroybase()
2469         elif evcode == FSCMOVE: # Supercommander moves
2470             schedule(FSCMOVE, 0.2777)
2471             if not game.ientesc and not istract and game.isatb != 1 and \
2472                    (not game.iscate or not game.justin):
2473                 supercommander()
2474         elif evcode == FDSPROB: # Move deep space probe
2475             schedule(FDSPROB, 0.01)
2476             if not game.probe.nexttok():
2477                 if not game.probe.quadrant().valid_quadrant() or \
2478                     game.state.galaxy[game.probe.quadrant().i][game.probe.quadrant().j].supernova:
2479                     # Left galaxy or ran into supernova
2480                     if communicating():
2481                         announce()
2482                         skip(1)
2483                         proutn(_("Lt. Uhura-  \"The deep space probe "))
2484                         if not game.probe.quadrant().valid_quadrant():
2485                             prout(_("has left the galaxy.\""))
2486                         else:
2487                             prout(_("is no longer transmitting.\""))
2488                     unschedule(FDSPROB)
2489                     continue
2490                 if communicating():
2491                     #announce()
2492                     skip(1)
2493                     prout(_("Lt. Uhura-  \"The deep space probe is now in Quadrant %s.\"") % game.probe.quadrant())
2494             pquad = game.probe.quadrant()
2495             pdest = game.state.galaxy[pquad.i][pquad.j]
2496             if communicating():
2497                 game.state.chart[pquad.i][pquad.j].klingons = pdest.klingons
2498                 game.state.chart[pquad.i][pquad.j].starbase = pdest.starbase
2499                 game.state.chart[pquad.i][pquad.j].stars = pdest.stars
2500                 pdest.charted = True
2501             game.probe.moves -= 1 # One less to travel
2502             if game.probe.arrived() and game.isarmed and pdest.stars:
2503                 supernova(game.probe)                # fire in the hole!
2504                 unschedule(FDSPROB)
2505                 if game.state.galaxy[pquad.i][pquad.j].supernova:
2506                     return
2507         elif evcode == FDISTR: # inhabited system issues distress call
2508             unschedule(FDISTR)
2509             # try a whole bunch of times to find something suitable
2510             for i in range(100):
2511                 # need a quadrant which is not the current one,
2512                 # which has some stars which are inhabited and
2513                 # not already under attack, which is not
2514                 # supernova'ed, and which has some Klingons in it
2515                 w = randplace(GALSIZE)
2516                 q = game.state.galaxy[w.i][w.j]
2517                 if not (game.quadrant == w or q.planet is None or \
2518                       not q.planet.inhabited or \
2519                       q.supernova or q.status!="secure" or q.klingons<=0):
2520                     break
2521             else:
2522                 # can't seem to find one; ignore this call
2523                 if game.idebug:
2524                     prout("=== Couldn't find location for distress event.")
2525                 continue
2526             # got one!!  Schedule its enslavement
2527             ev = schedule(FENSLV, expran(game.intime))
2528             ev.quadrant = w
2529             q.status = "distressed"
2530             # tell the captain about it if we can
2531             if communicating():
2532                 prout(_("Uhura- Captain, %s in Quadrant %s reports it is under attack") \
2533                         % (q.planet, repr(w)))
2534                 prout(_("by a Klingon invasion fleet."))
2535                 if cancelrest():
2536                     return
2537         elif evcode == FENSLV:                # starsystem is enslaved
2538             ev = unschedule(FENSLV)
2539             # see if current distress call still active
2540             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2541             if q.klingons <= 0:
2542                 q.status = "secure"
2543                 continue
2544             q.status = "enslaved"
2545
2546             # play stork and schedule the first baby
2547             ev2 = schedule(FREPRO, expran(2.0 * game.intime))
2548             ev2.quadrant = ev.quadrant
2549
2550             # report the disaster if we can
2551             if communicating():
2552                 prout(_("Uhura- We've lost contact with starsystem %s") % \
2553                         q.planet)
2554                 prout(_("in Quadrant %s.\n") % ev.quadrant)
2555         elif evcode == FREPRO:                # Klingon reproduces
2556             # If we ever switch to a real event queue, we'll need to
2557             # explicitly retrieve and restore the x and y.
2558             ev = schedule(FREPRO, expran(1.0 * game.intime))
2559             # see if current distress call still active
2560             q = game.state.galaxy[ev.quadrant.i][ev.quadrant.j]
2561             if q.klingons <= 0:
2562                 q.status = "secure"
2563                 continue
2564             if game.state.remkl >= MAXKLGAME:
2565                 continue                # full right now
2566             # reproduce one Klingon
2567             w = ev.quadrant
2568             m = Coord()
2569             if game.klhere >= MAXKLQUAD:
2570                 try:
2571                     # this quadrant not ok, pick an adjacent one
2572                     for m.i in range(w.i - 1, w.i + 2):
2573                         for m.j in range(w.j - 1, w.j + 2):
2574                             if not m.valid_quadrant():
2575                                 continue
2576                             q = game.state.galaxy[m.i][m.j]
2577                             # check for this quad ok (not full & no snova)
2578                             if q.klingons >= MAXKLQUAD or q.supernova:
2579                                 continue
2580                             raise JumpOut
2581                     # search for eligible quadrant failed
2582                     continue
2583                 except JumpOut:
2584                     w = m
2585             # deliver the child
2586             game.state.remkl += 1
2587             q.klingons += 1
2588             if game.quadrant == w:
2589                 game.klhere += 1
2590                 game.enemies.append(newkling())
2591             # recompute time left
2592             game.recompute()
2593             if communicating():
2594                 if game.quadrant == w:
2595                     prout(_("Spock- sensors indicate the Klingons have"))
2596                     prout(_("launched a warship from %s.") % q.planet)
2597                 else:
2598                     prout(_("Uhura- Starfleet reports increased Klingon activity"))
2599                     if q.planet != None:
2600                         proutn(_("near %s ") % q.planet)
2601                     prout(_("in Quadrant %s.") % w)
2602
2603 def wait():
2604     "Wait on events."
2605     game.ididit = False
2606     while True:
2607         key = scanner.nexttok()
2608         if key  != "IHEOL":
2609             break
2610         proutn(_("How long? "))
2611     scanner.chew()
2612     if key != "IHREAL":
2613         huh()
2614         return
2615     origTime = delay = scanner.real
2616     if delay <= 0.0:
2617         return
2618     if delay >= game.state.remtime or len(game.enemies) != 0:
2619         proutn(_("Are you sure? "))
2620         if not ja():
2621             return
2622     # Alternate resting periods (events) with attacks
2623     game.resting = True
2624     while True:
2625         if delay <= 0:
2626             game.resting = False
2627         if not game.resting:
2628             prout(_("%d stardates left.") % int(game.state.remtime))
2629             return
2630         temp = game.optime = delay
2631         if len(game.enemies):
2632             rtime = randreal(1.0, 2.0)
2633             if rtime < temp:
2634                 temp = rtime
2635             game.optime = temp
2636         if game.optime < delay:
2637             attack(torps_ok=False)
2638         if game.alldone:
2639             return
2640         events()
2641         game.ididit = True
2642         if game.alldone:
2643             return
2644         delay -= temp
2645         # Repair Deathray if long rest at starbase
2646         if origTime-delay >= 9.99 and game.condition == "docked":
2647             game.damage[DDRAY] = 0.0
2648         # leave if quadrant supernovas
2649         if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
2650             break
2651     game.resting = False
2652     game.optime = 0.0
2653
2654 def nova(nov):
2655     "Star goes nova."
2656     ncourse = (0.0, 10.5, 12.0, 1.5, 9.0, 0.0, 3.0, 7.5, 6.0, 4.5)
2657     newc = Coord(); neighbor = Coord(); bump = Coord(0, 0)
2658     if withprob(0.05):
2659         # Wow! We've supernova'ed
2660         supernova(game.quadrant)
2661         return
2662     # handle initial nova
2663     game.quad[nov.i][nov.j] = '.'
2664     prout(crmena(False, '*', "sector", nov) + _(" novas."))
2665     game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2666     game.state.starkl += 1
2667     # Set up queue to recursively trigger adjacent stars
2668     hits = [nov]
2669     kount = 0
2670     while hits:
2671         offset = Coord()
2672         start = hits.pop()
2673         for offset.i in range(-1, 1+1):
2674             for offset.j in range(-1, 1+1):
2675                 if offset.j == 0 and offset.i == 0:
2676                     continue
2677                 neighbor = start + offset
2678                 if not neighbor.valid_sector():
2679                     continue
2680                 iquad = game.quad[neighbor.i][neighbor.j]
2681                 # Empty space ends reaction
2682                 if iquad in ('.', '?', ' ', 'T', '#'):
2683                     pass
2684                 elif iquad == '*': # Affect another star
2685                     if withprob(0.05):
2686                         # This star supernovas
2687                         supernova(game.quadrant)
2688                         return
2689                     else:
2690                         hits.append(neighbor)
2691                         game.state.galaxy[game.quadrant.i][game.quadrant.j].stars -= 1
2692                         game.state.starkl += 1
2693                         proutn(crmena(True, '*', "sector", neighbor))
2694                         prout(_(" novas."))
2695                         game.quad[neighbor.i][neighbor.j] = '.'
2696                         kount += 1
2697                 elif iquad in ('P', '@'): # Destroy planet
2698                     game.state.galaxy[game.quadrant.i][game.quadrant.j].planet = None
2699                     if iquad == 'P':
2700                         game.state.nplankl += 1
2701                     else:
2702                         game.state.nworldkl += 1
2703                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2704                     game.iplnet.pclass = "destroyed"
2705                     game.iplnet = None
2706                     game.plnet.invalidate()
2707                     if game.landed:
2708                         finish(FPNOVA)
2709                         return
2710                     game.quad[neighbor.i][neighbor.j] = '.'
2711                 elif iquad == 'B': # Destroy base
2712                     game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase = False
2713                     game.state.baseq = [x for x in game.state.baseq if x!= game.quadrant]
2714                     game.base.invalidate()
2715                     game.state.basekl += 1
2716                     newcnd()
2717                     prout(crmena(True, 'B', "sector", neighbor) + _(" destroyed."))
2718                     game.quad[neighbor.i][neighbor.j] = '.'
2719                 elif iquad in ('E', 'F'): # Buffet ship
2720                     prout(_("***Starship buffeted by nova."))
2721                     if game.shldup:
2722                         if game.shield >= 2000.0:
2723                             game.shield -= 2000.0
2724                         else:
2725                             diff = 2000.0 - game.shield
2726                             game.energy -= diff
2727                             game.shield = 0.0
2728                             game.shldup = False
2729                             prout(_("***Shields knocked out."))
2730                             game.damage[DSHIELD] += 0.005*game.damfac*randreal()*diff
2731                     else:
2732                         game.energy -= 2000.0
2733                     if game.energy <= 0:
2734                         finish(FNOVA)
2735                         return
2736                     # add in course nova contributes to kicking starship
2737                     bump += (game.sector-hits[-1]).sgn()
2738                 elif iquad == 'K': # kill klingon
2739                     deadkl(neighbor, iquad, neighbor)
2740                 elif iquad in ('C','S','R'): # Damage/destroy big enemies
2741                     target = None
2742                     for ll in range(len(game.enemies)):
2743                         if game.enemies[ll].location == neighbor:
2744                             target = game.enemies[ll]
2745                             break
2746                     if target is not None:
2747                         target.power -= 800.0 # If firepower is lost, die
2748                         if target.power <= 0.0:
2749                             deadkl(neighbor, iquad, neighbor)
2750                             continue    # neighbor loop
2751                         # Else enemy gets flung by the blast wave
2752                         newc = neighbor + neighbor - hits[-1]
2753                         proutn(crmena(True, iquad, "sector", neighbor) + _(" damaged"))
2754                         if not newc.valid_sector():
2755                             # can't leave quadrant
2756                             skip(1)
2757                             continue
2758                         iquad1 = game.quad[newc.i][newc.j]
2759                         if iquad1 == ' ':
2760                             proutn(_(", blasted into ") + crmena(False, ' ', "sector", newc))
2761                             skip(1)
2762                             deadkl(neighbor, iquad, newc)
2763                             continue
2764                         if iquad1 != '.':
2765                             # can't move into something else
2766                             skip(1)
2767                             continue
2768                         proutn(_(", buffeted to Sector %s") % newc)
2769                         game.quad[neighbor.i][neighbor.j] = '.'
2770                         game.quad[newc.i][newc.j] = iquad
2771                         target.move(newc)
2772     # Starship affected by nova -- kick it away.
2773     dist = kount*0.1
2774     direc = ncourse[3*(bump.i+1)+bump.j+2]
2775     if direc == 0.0:
2776         dist = 0.0
2777     if dist == 0.0:
2778         return
2779     scourse = course(bearing=direc, distance=dist)
2780     game.optime = scourse.time(w=4)
2781     skip(1)
2782     prout(_("Force of nova displaces starship."))
2783     imove(scourse, noattack=True)
2784     game.optime = scourse.time(w=4)
2785     return
2786
2787 def supernova(w):
2788     "Star goes supernova."
2789     num = 0; npdead = 0
2790     if w != None:
2791         nq = copy.copy(w)
2792     else:
2793         # Scheduled supernova -- select star at random.
2794         nstars = 0
2795         nq = Coord()
2796         for nq.i in range(GALSIZE):
2797             for nq.j in range(GALSIZE):
2798                 nstars += game.state.galaxy[nq.i][nq.j].stars
2799         if stars == 0:
2800             return # nothing to supernova exists
2801         num = randrange(nstars) + 1
2802         for nq.i in range(GALSIZE):
2803             for nq.j in range(GALSIZE):
2804                 num -= game.state.galaxy[nq.i][nq.j].stars
2805                 if num <= 0:
2806                     break
2807             if num <=0:
2808                 break
2809         if game.idebug:
2810             proutn("=== Super nova here?")
2811             if ja():
2812                 nq = game.quadrant
2813     if not nq == game.quadrant or game.justin:
2814         # it isn't here, or we just entered (treat as enroute)
2815         if communicating():
2816             skip(1)
2817             prout(_("Message from Starfleet Command       Stardate %.2f") % game.state.date)
2818             prout(_("     Supernova in Quadrant %s; caution advised.") % nq)
2819     else:
2820         ns = Coord()
2821         # we are in the quadrant!
2822         num = randrange(game.state.galaxy[nq.i][nq.j].stars) + 1
2823         for ns.i in range(QUADSIZE):
2824             for ns.j in range(QUADSIZE):
2825                 if game.quad[ns.i][ns.j]=='*':
2826                     num -= 1
2827                     if num==0:
2828                         break
2829             if num==0:
2830                 break
2831         skip(1)
2832         prouts(_("***RED ALERT!  RED ALERT!"))
2833         skip(1)
2834         prout(_("***Incipient supernova detected at Sector %s") % ns)
2835         if (ns.i-game.sector.i)**2 + (ns.j-game.sector.j)**2 <= 2.1:
2836             proutn(_("Emergency override attempts t"))
2837             prouts("***************")
2838             skip(1)
2839             stars()
2840             game.alldone = True
2841     # destroy any Klingons in supernovaed quadrant
2842     kldead = game.state.galaxy[nq.i][nq.j].klingons
2843     game.state.galaxy[nq.i][nq.j].klingons = 0
2844     if nq == game.state.kscmdr:
2845         # did in the Supercommander!
2846         game.state.nscrem = game.state.kscmdr.i = game.state.kscmdr.j = game.isatb =  0
2847         game.iscate = False
2848         unschedule(FSCMOVE)
2849         unschedule(FSCDBAS)
2850     survivors = filter(lambda w: w != nq, game.state.kcmdr)
2851     comkills = len(game.state.kcmdr) - len(survivors)
2852     game.state.kcmdr = survivors
2853     kldead -= comkills
2854     if not game.state.kcmdr:
2855         unschedule(FTBEAM)
2856     game.state.remkl -= kldead
2857     # destroy Romulans and planets in supernovaed quadrant
2858     nrmdead = game.state.galaxy[nq.i][nq.j].romulans
2859     game.state.galaxy[nq.i][nq.j].romulans = 0
2860     game.state.nromrem -= nrmdead
2861     # Destroy planets
2862     for loop in range(game.inplan):
2863         if game.state.planets[loop].quadrant == nq:
2864             game.state.planets[loop].pclass = "destroyed"
2865             npdead += 1
2866     # Destroy any base in supernovaed quadrant
2867     game.state.baseq = [x for x in game.state.baseq if x != nq]
2868     # If starship caused supernova, tally up destruction
2869     if w != None:
2870         game.state.starkl += game.state.galaxy[nq.i][nq.j].stars
2871         game.state.basekl += game.state.galaxy[nq.i][nq.j].starbase
2872         game.state.nplankl += npdead
2873     # mark supernova in galaxy and in star chart
2874     if game.quadrant == nq or communicating():
2875         game.state.galaxy[nq.i][nq.j].supernova = True
2876     # If supernova destroys last Klingons give special message
2877     if game.unwon()==0 and not nq == game.quadrant:
2878         skip(2)
2879         if w is None:
2880             prout(_("Lucky you!"))
2881         proutn(_("A supernova in %s has just destroyed the last Klingons.") % nq)
2882         finish(FWON)
2883         return
2884     # if some Klingons remain, continue or die in supernova
2885     if game.alldone:
2886         finish(FSNOVAED)
2887     return
2888
2889 # Code from finish.c ends here.
2890
2891 def selfdestruct():
2892     "Self-destruct maneuver. Finish with a BANG!"
2893     scanner.chew()
2894     if damaged(DCOMPTR):
2895         prout(_("Computer damaged; cannot execute destruct sequence."))
2896         return
2897     prouts(_("---WORKING---")); skip(1)
2898     prouts(_("SELF-DESTRUCT-SEQUENCE-ACTIVATED")); skip(1)
2899     prouts("   10"); skip(1)
2900     prouts("       9"); skip(1)
2901     prouts("          8"); skip(1)
2902     prouts("             7"); skip(1)
2903     prouts("                6"); skip(1)
2904     skip(1)
2905     prout(_("ENTER-CORRECT-PASSWORD-TO-CONTINUE-"))
2906     skip(1)
2907     prout(_("SELF-DESTRUCT-SEQUENCE-OTHERWISE-"))
2908     skip(1)
2909     prout(_("SELF-DESTRUCT-SEQUENCE-WILL-BE-ABORTED"))
2910     skip(1)
2911     scanner.nexttok()
2912     if game.passwd != scanner.token:
2913         prouts(_("PASSWORD-REJECTED;"))
2914         skip(1)
2915         prouts(_("CONTINUITY-EFFECTED"))
2916         skip(2)
2917         return
2918     prouts(_("PASSWORD-ACCEPTED")); skip(1)
2919     prouts("                   5"); skip(1)
2920     prouts("                      4"); skip(1)
2921     prouts("                         3"); skip(1)
2922     prouts("                            2"); skip(1)
2923     prouts("                              1"); skip(1)
2924     if withprob(0.15):
2925         prouts(_("GOODBYE-CRUEL-WORLD"))
2926         skip(1)
2927     kaboom()
2928
2929 def kaboom():
2930     stars()
2931     if game.ship=='E':
2932         prouts("***")
2933     prouts(_("********* Entropy of %s maximized *********") % crmshp())
2934     skip(1)
2935     stars()
2936     skip(1)
2937     if len(game.enemies) != 0:
2938         whammo = 25.0 * game.energy
2939         for l in range(len(game.enemies)):
2940             if game.enemies[l].power*game.enemies[l].kdist <= whammo:
2941                 deadkl(game.enemies[l].location, game.quad[game.enemies[l].location.i][game.enemies[l].location.j], game.enemies[l].location)
2942     finish(FDILITHIUM)
2943
2944 def killrate():
2945     "Compute our rate of kils over time."
2946     elapsed = game.state.date - game.indate
2947     if elapsed == 0:        # Avoid divide-by-zero error if calculated on turn 0
2948         return 0
2949     else:
2950         starting = (game.inkling + game.incom + game.inscom)
2951         remaining = game.unwon()
2952         return (starting - remaining)/elapsed
2953
2954 def badpoints():
2955     "Compute demerits."
2956     badpt = 5.0*game.state.starkl + \
2957             game.casual + \
2958             10.0*game.state.nplankl + \
2959             300*game.state.nworldkl + \
2960             45.0*game.nhelp +\
2961             100.0*game.state.basekl +\
2962             3.0*game.abandoned +\
2963             100*game.ncviol
2964     if game.ship == 'F':
2965         badpt += 100.0
2966     elif game.ship is None:
2967         badpt += 200.0
2968     return badpt
2969
2970 def finish(ifin):
2971     # end the game, with appropriate notifications
2972     igotit = False
2973     game.alldone = True
2974     skip(3)
2975     prout(_("It is stardate %.1f.") % game.state.date)
2976     skip(1)
2977     if ifin == FWON: # Game has been won
2978         if game.state.nromrem != 0:
2979             prout(_("The remaining %d Romulans surrender to Starfleet Command.") %
2980                   game.state.nromrem)
2981
2982         prout(_("You have smashed the Klingon invasion fleet and saved"))
2983         prout(_("the Federation."))
2984         if game.alive and game.brigcapacity-game.brigfree > 0:
2985             game.kcaptured += game.brigcapacity-game.brigfree
2986             prout(_("The %d captured Klingons are transferred to Star Fleet Command.") % (game.brigcapacity-game.brigfree))
2987         game.gamewon = True
2988         if game.alive:
2989             badpt = badpoints()
2990             if badpt < 100.0:
2991                 badpt = 0.0        # Close enough!
2992             # killsPerDate >= RateMax
2993             if game.state.date-game.indate < 5.0 or \
2994                 killrate() >= 0.1*game.skill*(game.skill+1.0) + 0.1 + 0.008*badpt:
2995                 skip(1)
2996                 prout(_("In fact, you have done so well that Starfleet Command"))
2997                 if game.skill == SKILL_NOVICE:
2998                     prout(_("promotes you one step in rank from \"Novice\" to \"Fair\"."))
2999                 elif game.skill == SKILL_FAIR:
3000                     prout(_("promotes you one step in rank from \"Fair\" to \"Good\"."))
3001                 elif game.skill == SKILL_GOOD:
3002                     prout(_("promotes you one step in rank from \"Good\" to \"Expert\"."))
3003                 elif game.skill == SKILL_EXPERT:
3004                     prout(_("promotes you to Commodore Emeritus."))
3005                     skip(1)
3006                     prout(_("Now that you think you're really good, try playing"))
3007                     prout(_("the \"Emeritus\" game. It will splatter your ego."))
3008                 elif game.skill == SKILL_EMERITUS:
3009                     skip(1)
3010                     proutn(_("Computer-  "))
3011                     prouts(_("ERROR-ERROR-ERROR-ERROR"))
3012                     skip(2)
3013                     prouts(_("  YOUR-SKILL-HAS-EXCEEDED-THE-CAPACITY-OF-THIS-PROGRAM"))
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-SURVIVE"))
3020                     skip(1)
3021                     prouts(_("  THIS-PROGRAM-MUST?- MUST ? - SUR? ? -?  VI"))
3022                     skip(2)
3023                     prout(_("Now you can retire and write your own Star Trek game!"))
3024                     skip(1)
3025                 elif game.skill >= SKILL_EXPERT:
3026                     if game.thawed and not game.idebug:
3027                         prout(_("You cannot get a citation, so..."))
3028                     else:
3029                         proutn(_("Do you want your Commodore Emeritus Citation printed? "))
3030                         scanner.chew()
3031                         if ja():
3032                             igotit = True
3033             # Only grant long life if alive (original didn't!)
3034             skip(1)
3035             prout(_("LIVE LONG AND PROSPER."))
3036         score()
3037         if igotit:
3038             plaque()
3039         return
3040     elif ifin == FDEPLETE: # Federation Resources Depleted
3041         prout(_("Your time has run out and the Federation has been"))
3042         prout(_("conquered.  Your starship is now Klingon property,"))
3043         prout(_("and you are put on trial as a war criminal.  On the"))
3044         proutn(_("basis of your record, you are "))
3045         if game.unwon()*3.0 > (game.inkling + game.incom + game.inscom):
3046             prout(_("acquitted."))
3047             skip(1)
3048             prout(_("LIVE LONG AND PROSPER."))
3049         else:
3050             prout(_("found guilty and"))
3051             prout(_("sentenced to death by slow torture."))
3052             game.alive = False
3053         score()
3054         return
3055     elif ifin == FLIFESUP:
3056         prout(_("Your life support reserves have run out, and"))
3057         prout(_("you die of thirst, starvation, and asphyxiation."))
3058         prout(_("Your starship is a derelict in space."))
3059     elif ifin == FNRG:
3060         prout(_("Your energy supply is exhausted."))
3061         skip(1)
3062         prout(_("Your starship is a derelict in space."))
3063     elif ifin == FBATTLE:
3064         prout(_("The %s has been destroyed in battle.") % crmshp())
3065         skip(1)
3066         prout(_("Dulce et decorum est pro patria mori."))
3067     elif ifin == FNEG3:
3068         prout(_("You have made three attempts to cross the negative energy"))
3069         prout(_("barrier which surrounds the galaxy."))
3070         skip(1)
3071         prout(_("Your navigation is abominable."))
3072         score()
3073     elif ifin == FNOVA:
3074         prout(_("Your starship has been destroyed by a nova."))
3075         prout(_("That was a great shot."))
3076         skip(1)
3077     elif ifin == FSNOVAED:
3078         prout(_("The %s has been fried by a supernova.") % crmshp())
3079         prout(_("...Not even cinders remain..."))
3080     elif ifin == FABANDN:
3081         prout(_("You have been captured by the Klingons. If you still"))
3082         prout(_("had a starbase to be returned to, you would have been"))
3083         prout(_("repatriated and given another chance. Since you have"))
3084         prout(_("no starbases, you will be mercilessly tortured to death."))
3085     elif ifin == FDILITHIUM:
3086         prout(_("Your starship is now an expanding cloud of subatomic particles"))
3087     elif ifin == FMATERIALIZE:
3088         prout(_("Starbase was unable to re-materialize your starship."))
3089         prout(_("Sic transit gloria mundi"))
3090     elif ifin == FPHASER:
3091         prout(_("The %s has been cremated by its own phasers.") % crmshp())
3092     elif ifin == FLOST:
3093         prout(_("You and your landing party have been"))
3094         prout(_("converted to energy, dissipating through space."))
3095     elif ifin == FMINING:
3096         prout(_("You are left with your landing party on"))
3097         prout(_("a wild jungle planet inhabited by primitive cannibals."))
3098         skip(1)
3099         prout(_("They are very fond of \"Captain Kirk\" soup."))
3100         skip(1)
3101         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3102     elif ifin == FDPLANET:
3103         prout(_("You and your mining party perish."))
3104         skip(1)
3105         prout(_("That was a great shot."))
3106         skip(1)
3107     elif ifin == FSSC:
3108         prout(_("The Galileo is instantly annihilated by the supernova."))
3109         prout(_("You and your mining party are atomized."))
3110         skip(1)
3111         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3112         prout(_("joins the Romulans, wreaking terror on the Federation."))
3113     elif ifin == FPNOVA:
3114         prout(_("You and your mining party are atomized."))
3115         skip(1)
3116         prout(_("Mr. Spock takes command of the %s and") % crmshp())
3117         prout(_("joins the Romulans, wreaking terror on the Federation."))
3118     elif ifin == FSTRACTOR:
3119         prout(_("The shuttle craft Galileo is also caught,"))
3120         prout(_("and breaks up under the strain."))
3121         skip(1)
3122         prout(_("Your debris is scattered for millions of miles."))
3123         prout(_("Without your leadership, the %s is destroyed.") % crmshp())
3124     elif ifin == FDRAY:
3125         prout(_("The mutants attack and kill Spock."))
3126         prout(_("Your ship is captured by Klingons, and"))
3127         prout(_("your crew is put on display in a Klingon zoo."))
3128     elif ifin == FTRIBBLE:
3129         prout(_("Tribbles consume all remaining water,"))
3130         prout(_("food, and oxygen on your ship."))
3131         skip(1)
3132         prout(_("You die of thirst, starvation, and asphyxiation."))
3133         prout(_("Your starship is a derelict in space."))
3134     elif ifin == FHOLE:
3135         prout(_("Your ship is drawn to the center of the black hole."))
3136         prout(_("You are crushed into extremely dense matter."))
3137     elif ifin == FCLOAK:
3138         game.ncviol += 1
3139         prout(_("You have violated the Treaty of Algeron."))
3140         prout(_("The Romulan Empire can never trust you again."))
3141     elif ifin == FCREW:
3142         prout(_("Your last crew member has died."))
3143     if ifin != FWON and ifin != FCLOAK and game.iscloaked:
3144         prout(_("Your ship was cloaked so your subspace radio did not receive anything."))
3145         prout(_("You may have missed some warning messages."))
3146         skip(1)
3147     if game.ship == 'F':
3148         game.ship = None
3149     elif game.ship == 'E':
3150         game.ship = 'F'
3151     game.alive = False
3152     if game.unwon() != 0:
3153         goodies = game.state.remres/game.inresor
3154         baddies = (game.state.remkl + 2.0*len(game.state.kcmdr))/(game.inkling+2.0*game.incom)
3155         if goodies/baddies >= randreal(1.0, 1.5):
3156             prout(_("As a result of your actions, a treaty with the Klingon"))
3157             prout(_("Empire has been signed. The terms of the treaty are"))
3158             if goodies/baddies >= randreal(3.0):
3159                 prout(_("favorable to the Federation."))
3160                 skip(1)
3161                 prout(_("Congratulations!"))
3162             else:
3163                 prout(_("highly unfavorable to the Federation."))
3164         else:
3165             prout(_("The Federation will be destroyed."))
3166     else:
3167         prout(_("Since you took the last Klingon with you, you are a"))
3168         prout(_("martyr and a hero. Someday maybe they'll erect a"))
3169         prout(_("statue in your memory. Rest in peace, and try not"))
3170         prout(_("to think about pigeons."))
3171         game.gamewon = True
3172     score()
3173     scanner.chew()      # Clean up leftovers
3174
3175 def score():
3176     "Compute player's score."
3177     timused = game.state.date - game.indate
3178     if (timused == 0 or game.unwon() != 0) and timused < 5.0:
3179         timused = 5.0
3180     game.perdate = killrate()
3181     ithperd = 500*game.perdate + 0.5
3182     iwon = 0
3183     if game.gamewon:
3184         iwon = 100*game.skill
3185     if game.ship == 'E':
3186         klship = 0
3187     elif game.ship == 'F':
3188         klship = 1
3189     else:
3190         klship = 2
3191     game.score = 10*(game.inkling - game.state.remkl) \
3192              + 50*(game.incom - len(game.state.kcmdr)) \
3193              + ithperd + iwon \
3194              + 20*(game.inrom - game.state.nromrem) \
3195              + 200*(game.inscom - game.state.nscrem) \
3196                  - game.state.nromrem \
3197              + 3 * game.kcaptured \
3198              - badpoints()
3199     if not game.alive:
3200         game.score -= 200
3201     skip(2)
3202     prout(_("Your score --"))
3203     if game.inrom - game.state.nromrem:
3204         prout(_("%6d Romulans destroyed                 %5d") %
3205               (game.inrom - game.state.nromrem, 20*(game.inrom - game.state.nromrem)))
3206     if game.state.nromrem and game.gamewon:
3207         prout(_("%6d Romulans captured                  %5d") %
3208               (game.state.nromrem, game.state.nromrem))
3209     if game.inkling - game.state.remkl:
3210         prout(_("%6d ordinary Klingons destroyed        %5d") %
3211               (game.inkling - game.state.remkl, 10*(game.inkling - game.state.remkl)))
3212     if game.incom - len(game.state.kcmdr):
3213         prout(_("%6d Klingon commanders destroyed       %5d") %
3214               (game.incom - len(game.state.kcmdr), 50*(game.incom - len(game.state.kcmdr))))
3215     if game.kcaptured:
3216         prout(_("%d Klingons captured                   %5d") %
3217               (game.kcaptured, 3 * game.kcaptured))
3218     if game.inscom - game.state.nscrem:
3219         prout(_("%6d Super-Commander destroyed          %5d") %
3220               (game.inscom - game.state.nscrem, 200*(game.inscom - game.state.nscrem)))
3221     if ithperd:
3222         prout(_("%6.2f Klingons per stardate              %5d") %
3223               (game.perdate, ithperd))
3224     if game.state.starkl:
3225         prout(_("%6d stars destroyed by your action     %5d") %
3226               (game.state.starkl, -5*game.state.starkl))
3227     if game.state.nplankl:
3228         prout(_("%6d planets destroyed by your action   %5d") %
3229               (game.state.nplankl, -10*game.state.nplankl))
3230     if (game.options & OPTION_WORLDS) and game.state.nworldkl:
3231         prout(_("%6d inhabited planets destroyed by your action   %5d") %
3232               (game.state.nworldkl, -300*game.state.nworldkl))
3233     if game.state.basekl:
3234         prout(_("%6d bases destroyed by your action     %5d") %
3235               (game.state.basekl, -100*game.state.basekl))
3236     if game.nhelp:
3237         prout(_("%6d calls for help from starbase       %5d") %
3238               (game.nhelp, -45*game.nhelp))
3239     if game.casual:
3240         prout(_("%6d casualties incurred                %5d") %
3241               (game.casual, -game.casual))
3242     if game.abandoned:
3243         prout(_("%6d crew abandoned in space            %5d") %
3244               (game.abandoned, -3*game.abandoned))
3245     if klship:
3246         prout(_("%6d ship(s) lost or destroyed          %5d") %
3247               (klship, -100*klship))
3248     if game.ncviol > 0:
3249         if ncviol == 1:
3250             prout(_("1 Treaty of Algeron violation          -100"))
3251         else:
3252             prout(_("%6d Treaty of Algeron violations       %5d\n") %
3253                   (ncviol, -100*ncviol))
3254     if not game.alive:
3255         prout(_("Penalty for getting yourself killed        -200"))
3256     if game.gamewon:
3257         proutn(_("Bonus for winning "))
3258         if game.skill   == SKILL_NOVICE:        proutn(_("Novice game  "))
3259         elif game.skill == SKILL_FAIR:          proutn(_("Fair game    "))
3260         elif game.skill ==  SKILL_GOOD:         proutn(_("Good game    "))
3261         elif game.skill ==  SKILL_EXPERT:        proutn(_("Expert game  "))
3262         elif game.skill ==  SKILL_EMERITUS:        proutn(_("Emeritus game"))
3263         prout("           %5d" % iwon)
3264     skip(1)
3265     prout(_("TOTAL SCORE                               %5d") % game.score)
3266
3267 def plaque():
3268     "Emit winner's commemmorative plaque."
3269     skip(2)
3270     while True:
3271         proutn(_("File or device name for your plaque: "))
3272         winner = cgetline()
3273         try:
3274             fp = open(winner, "w")
3275             break
3276         except IOError:
3277             prout(_("Invalid name."))
3278
3279     proutn(_("Enter name to go on plaque (up to 30 characters): "))
3280     winner = cgetline()
3281     # The 38 below must be 64 for 132-column paper
3282     nskip = 38 - len(winner)/2
3283     fp.write("\n\n\n\n")
3284     # --------DRAW ENTERPRISE PICTURE.
3285     fp.write("                                       EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" )
3286     fp.write("                                      EEE                      E  : :                                         :  E\n" )
3287     fp.write("                                    EE   EEE                   E  : :                   NCC-1701              :  E\n")
3288     fp.write("EEEEEEEEEEEEEEEE        EEEEEEEEEEEEEEE  : :                              : E\n")
3289     fp.write(" E                                     EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n")
3290     fp.write("                      EEEEEEEEE               EEEEEEEEEEEEE                 E  E\n")
3291     fp.write("                               EEEEEEE   EEEEE    E          E              E  E\n")
3292     fp.write("                                      EEE           E          E            E  E\n")
3293     fp.write("                                                       E         E          E  E\n")
3294     fp.write("                                                         EEEEEEEEEEEEE      E  E\n")
3295     fp.write("                                                      EEE :           EEEEEEE  EEEEEEEE\n")
3296     fp.write("                                                    :E    :                 EEEE       E\n")
3297     fp.write("                                                   .-E   -:-----                       E\n")
3298     fp.write("                                                    :E    :                            E\n")
3299     fp.write("                                                      EE  :                    EEEEEEEE\n")
3300     fp.write("                                                       EEEEEEEEEEEEEEEEEEEEEEE\n")
3301     fp.write("\n\n\n")
3302     fp.write(_("                                                       U. S. S. ENTERPRISE\n"))
3303     fp.write("\n\n\n\n")
3304     fp.write(_("                                  For demonstrating outstanding ability as a starship captain\n"))
3305     fp.write("\n")
3306     fp.write(_("                                                Starfleet Command bestows to you\n"))
3307     fp.write("\n")
3308     fp.write("%*s%s\n\n" % (nskip, "", winner))
3309     fp.write(_("                                                           the rank of\n\n"))
3310     fp.write(_("                                                       \"Commodore Emeritus\"\n\n"))
3311     fp.write("                                                          ")
3312     if game.skill ==  SKILL_EXPERT:
3313         fp.write(_(" Expert level\n\n"))
3314     elif game.skill == SKILL_EMERITUS:
3315         fp.write(_("Emeritus level\n\n"))
3316     else:
3317         fp.write(_(" Cheat level\n\n"))
3318     timestring = time.ctime()
3319     fp.write(_("                                                 This day of %.6s %.4s, %.8s\n\n") %
3320              (timestring+4, timestring+20, timestring+11))
3321     fp.write(_("                                                        Your score:  %d\n\n") % game.score)
3322     fp.write(_("                                                    Klingons per stardate:  %.2f\n") % game.perdate)
3323     fp.close()
3324
3325 # Code from io.c begins here
3326
3327 rows = linecount = 0        # for paging
3328 stdscr = None
3329 replayfp = None
3330 fullscreen_window = None
3331 srscan_window     = None   # Short range scan
3332 report_window     = None   # Report legends for status window
3333 status_window     = None   # The status window itself
3334 lrscan_window     = None   # Long range scan
3335 message_window    = None   # Main window for scrolling text
3336 prompt_window     = None   # Prompt window at bottom of display
3337 curwnd = None
3338
3339 def iostart():
3340     global stdscr, rows
3341     # for some recent versions of python2, the following enables UTF8
3342     # for the older ones we probably need to set C locale, and python3
3343     # has no problems at all
3344     if sys.version_info[0] < 3:
3345         locale.setlocale(locale.LC_ALL, "")
3346     gettext.bindtextdomain("sst", "/usr/local/share/locale")
3347     gettext.textdomain("sst")
3348     if not (game.options & OPTION_CURSES):
3349         ln_env = os.getenv("LINES")
3350         if ln_env:
3351             rows = ln_env
3352         else:
3353             rows = 25
3354     else:
3355         stdscr = curses.initscr()
3356         stdscr.keypad(True)
3357         curses.nonl()
3358         curses.cbreak()
3359         if game.options & OPTION_COLOR:
3360             curses.start_color()
3361             curses.use_default_colors()
3362             curses.init_pair(curses.COLOR_BLACK,   curses.COLOR_BLACK, -1)
3363             curses.init_pair(curses.COLOR_GREEN,   curses.COLOR_GREEN, -1)
3364             curses.init_pair(curses.COLOR_RED,     curses.COLOR_RED, -1)
3365             curses.init_pair(curses.COLOR_CYAN,    curses.COLOR_CYAN, -1)
3366             curses.init_pair(curses.COLOR_WHITE,   curses.COLOR_WHITE, -1)
3367             curses.init_pair(curses.COLOR_MAGENTA, curses.COLOR_MAGENTA, -1)
3368             curses.init_pair(curses.COLOR_BLUE,    curses.COLOR_BLUE, -1)
3369             curses.init_pair(curses.COLOR_YELLOW,  curses.COLOR_YELLOW, -1)
3370         global fullscreen_window, srscan_window, report_window, status_window
3371         global lrscan_window, message_window, prompt_window
3372         (rows, _columns)   = stdscr.getmaxyx()
3373         fullscreen_window = stdscr
3374         srscan_window     = curses.newwin(12, 25, 0,       0)
3375         report_window     = curses.newwin(11, 0,  1,       25)
3376         status_window     = curses.newwin(10, 0,  1,       39)
3377         lrscan_window     = curses.newwin(5,  0,  0,       64)
3378         message_window    = curses.newwin(0,  0,  12,      0)
3379         prompt_window     = curses.newwin(1,  0,  rows-2,  0)
3380         message_window.scrollok(True)
3381         setwnd(fullscreen_window)
3382
3383 def ioend():
3384     "Wrap up I/O."
3385     if game.options & OPTION_CURSES:
3386         stdscr.keypad(False)
3387         curses.echo()
3388         curses.nocbreak()
3389         curses.endwin()
3390
3391 def waitfor():
3392     "Wait for user action -- OK to do nothing if on a TTY"
3393     if game.options & OPTION_CURSES:
3394         stdscr.getch()
3395
3396 def announce():
3397     skip(1)
3398     prouts(_("[ANNOUNCEMENT ARRIVING...]"))
3399     skip(1)
3400
3401 def pause_game():
3402     if game.skill > SKILL_FAIR:
3403         prompt = _("[CONTINUE?]")
3404     else:
3405         prompt = _("[PRESS ENTER TO CONTINUE]")
3406
3407     if game.options & OPTION_CURSES:
3408         drawmaps(0)
3409         setwnd(prompt_window)
3410         prompt_window.clear()
3411         prompt_window.addstr(prompt)
3412         prompt_window.getstr()
3413         prompt_window.clear()
3414         prompt_window.refresh()
3415         setwnd(message_window)
3416     else:
3417         global linecount
3418         sys.stdout.write('\n')
3419         proutn(prompt)
3420         if not replayfp:
3421             my_input()
3422         sys.stdout.write('\n' * rows)
3423         linecount = 0
3424
3425 def skip(i):
3426     "Skip i lines.  Pause game if this would cause a scrolling event."
3427     for _dummy in range(i):
3428         if game.options & OPTION_CURSES:
3429             (y, _x) = curwnd.getyx()
3430             try:
3431                 curwnd.move(y+1, 0)
3432             except curses.error:
3433                 pass
3434         else:
3435             global linecount
3436             linecount += 1
3437             if rows and linecount >= rows:
3438                 pause_game()
3439             else:
3440                 sys.stdout.write('\n')
3441
3442 def proutn(proutntline):
3443     "Utter a line with no following line feed."
3444     if game.options & OPTION_CURSES:
3445         (y, x) = curwnd.getyx()
3446         (my, _mx) = curwnd.getmaxyx()
3447         if curwnd == message_window and y >= my - 2:
3448             pause_game()
3449             clrscr()
3450         # Uncomment this to debug curses problems
3451         if logfp:
3452             logfp.write("#curses: at %s proutn(%s)\n" % ((y, x), repr(proutntline)))
3453         curwnd.addstr(proutntline)
3454         curwnd.refresh()
3455     else:
3456         sys.stdout.write(proutntline)
3457         sys.stdout.flush()
3458
3459 def prout(proutline):
3460     proutn(proutline)
3461     skip(1)
3462
3463 def prouts(proutsline):
3464     "Emit slowly!"
3465     for c in proutsline:
3466         if not replayfp or replayfp.closed:        # Don't slow down replays
3467             time.sleep(0.03)
3468         proutn(c)
3469         if game.options & OPTION_CURSES:
3470             curwnd.refresh()
3471         else:
3472             sys.stdout.flush()
3473     if not replayfp or replayfp.closed:
3474         time.sleep(0.03)
3475
3476 def cgetline():
3477     "Get a line of input."
3478     if game.options & OPTION_CURSES:
3479         linein = curwnd.getstr() + "\n"
3480         curwnd.refresh()
3481     else:
3482         if replayfp and not replayfp.closed:
3483             while True:
3484                 linein = replayfp.readline()
3485                 proutn(linein)
3486                 if linein == '':
3487                     prout("*** Replay finished")
3488                     replayfp.close()
3489                     break
3490                 elif linein[0] != "#":
3491                     break
3492         else:
3493             try:
3494                 linein = my_input() + "\n"
3495             except EOFError:
3496                 prout("")
3497                 sys.exit(0)
3498     if logfp:
3499         logfp.write(linein)
3500     return linein
3501
3502 def setwnd(wnd):
3503     "Change windows -- OK for this to be a no-op in tty mode."
3504     global curwnd
3505     if game.options & OPTION_CURSES:
3506         # Uncomment this to debug curses problems
3507         if logfp:
3508             if wnd == fullscreen_window:
3509                 legend = "fullscreen"
3510             elif wnd == srscan_window:
3511                 legend = "srscan"
3512             elif wnd == report_window:
3513                 legend = "report"
3514             elif wnd == status_window:
3515                 legend = "status"
3516             elif wnd == lrscan_window:
3517                 legend = "lrscan"
3518             elif wnd == message_window:
3519                 legend = "message"
3520             elif wnd == prompt_window:
3521                 legend = "prompt"
3522             else:
3523                 legend = "unknown"
3524             logfp.write("#curses: setwnd(%s)\n" % legend)
3525         curwnd = wnd
3526         # Some curses implementations get confused when you try this.
3527         try:
3528             curses.curs_set(wnd in (fullscreen_window, message_window, prompt_window))
3529         except curses.error:
3530             pass
3531
3532 def clreol():
3533     "Clear to end of line -- can be a no-op in tty mode"
3534     if game.options & OPTION_CURSES:
3535         curwnd.clrtoeol()
3536         curwnd.refresh()
3537
3538 def clrscr():
3539     "Clear screen -- can be a no-op in tty mode."
3540     global linecount
3541     if game.options & OPTION_CURSES:
3542         curwnd.clear()
3543         curwnd.move(0, 0)
3544         curwnd.refresh()
3545     linecount = 0
3546
3547 def textcolor(color=DEFAULT):
3548     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3549         if color == DEFAULT:
3550             curwnd.attrset(0)
3551         elif color ==  BLACK:
3552             curwnd.attron(curses.color_pair(curses.COLOR_BLACK))
3553         elif color ==  BLUE:
3554             curwnd.attron(curses.color_pair(curses.COLOR_BLUE))
3555         elif color ==  GREEN:
3556             curwnd.attron(curses.color_pair(curses.COLOR_GREEN))
3557         elif color ==  CYAN:
3558             curwnd.attron(curses.color_pair(curses.COLOR_CYAN))
3559         elif color ==  RED:
3560             curwnd.attron(curses.color_pair(curses.COLOR_RED))
3561         elif color ==  MAGENTA:
3562             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA))
3563         elif color ==  BROWN:
3564             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW))
3565         elif color ==  LIGHTGRAY:
3566             curwnd.attron(curses.color_pair(curses.COLOR_WHITE))
3567         elif color ==  DARKGRAY:
3568             curwnd.attron(curses.color_pair(curses.COLOR_BLACK) | curses.A_BOLD)
3569         elif color ==  LIGHTBLUE:
3570             curwnd.attron(curses.color_pair(curses.COLOR_BLUE) | curses.A_BOLD)
3571         elif color ==  LIGHTGREEN:
3572             curwnd.attron(curses.color_pair(curses.COLOR_GREEN) | curses.A_BOLD)
3573         elif color ==  LIGHTCYAN:
3574             curwnd.attron(curses.color_pair(curses.COLOR_CYAN) | curses.A_BOLD)
3575         elif color ==  LIGHTRED:
3576             curwnd.attron(curses.color_pair(curses.COLOR_RED) | curses.A_BOLD)
3577         elif color ==  LIGHTMAGENTA:
3578             curwnd.attron(curses.color_pair(curses.COLOR_MAGENTA) | curses.A_BOLD)
3579         elif color ==  YELLOW:
3580             curwnd.attron(curses.color_pair(curses.COLOR_YELLOW) | curses.A_BOLD)
3581         elif color ==  WHITE:
3582             curwnd.attron(curses.color_pair(curses.COLOR_WHITE) | curses.A_BOLD)
3583
3584 def highvideo():
3585     if (game.options & OPTION_COLOR) and (game.options & OPTION_CURSES):
3586         curwnd.attron(curses.A_REVERSE)
3587
3588 #
3589 # Things past this point have policy implications.
3590 #
3591
3592 def drawmaps(mode):
3593     "Hook to be called after moving to redraw maps."
3594     if game.options & OPTION_CURSES:
3595         if mode == 1:
3596             sensor()
3597         setwnd(srscan_window)
3598         curwnd.move(0, 0)
3599         srscan()
3600         if mode != 2:
3601             setwnd(status_window)
3602             status_window.clear()
3603             status_window.move(0, 0)
3604             setwnd(report_window)
3605             report_window.clear()
3606             report_window.move(0, 0)
3607             status()
3608             setwnd(lrscan_window)
3609             lrscan_window.clear()
3610             lrscan_window.move(0, 0)
3611             lrscan(silent=False)
3612
3613 def put_srscan_sym(w, sym):
3614     "Emit symbol for short-range scan."
3615     srscan_window.move(w.i+1, w.j*2+2)
3616     srscan_window.addch(sym)
3617     srscan_window.refresh()
3618
3619 def boom(w):
3620     "Enemy fall down, go boom."
3621     if game.options & OPTION_CURSES:
3622         drawmaps(0)
3623         setwnd(srscan_window)
3624         srscan_window.attron(curses.A_REVERSE)
3625         put_srscan_sym(w, game.quad[w.i][w.j])
3626         #sound(500)
3627         #time.sleep(1.0)
3628         #nosound()
3629         srscan_window.attroff(curses.A_REVERSE)
3630         put_srscan_sym(w, game.quad[w.i][w.j])
3631         curses.delay_output(500)
3632         setwnd(message_window)
3633
3634 def warble():
3635     "Sound and visual effects for teleportation."
3636     if game.options & OPTION_CURSES:
3637         drawmaps(2)
3638         setwnd(message_window)
3639         #sound(50)
3640     prouts("     . . . . .     ")
3641     if game.options & OPTION_CURSES:
3642         #curses.delay_output(1000)
3643         #nosound()
3644         pass
3645
3646 def tracktorpedo(w, step, i, n, iquad):
3647     "Torpedo-track animation."
3648     if not game.options & OPTION_CURSES:
3649         if step == 1:
3650             if n != 1:
3651                 skip(1)
3652                 proutn(_("Track for torpedo number %d-  ") % (i+1))
3653             else:
3654                 skip(1)
3655                 proutn(_("Torpedo track- "))
3656         elif step==4 or step==9:
3657             skip(1)
3658         proutn("%s   " % w)
3659     else:
3660         if not damaged(DSRSENS) or game.condition=="docked":
3661             if i != 0 and step == 1:
3662                 drawmaps(2)
3663                 time.sleep(0.4)
3664             if (iquad=='.') or (iquad==' '):
3665                 put_srscan_sym(w, '+')
3666                 #sound(step*10)
3667                 #time.sleep(0.1)
3668                 #nosound()
3669                 put_srscan_sym(w, iquad)
3670             else:
3671                 curwnd.attron(curses.A_REVERSE)
3672                 put_srscan_sym(w, iquad)
3673                 #sound(500)
3674                 #time.sleep(1.0)
3675                 #nosound()
3676                 curwnd.attroff(curses.A_REVERSE)
3677                 put_srscan_sym(w, iquad)
3678         else:
3679             proutn("%s   " % w)
3680
3681 def makechart():
3682     "Display the current galaxy chart."
3683     if game.options & OPTION_CURSES:
3684         setwnd(message_window)
3685         message_window.clear()
3686     chart()
3687     if game.options & OPTION_TTY:
3688         skip(1)
3689
3690 NSYM        = 14
3691
3692 def prstat(txt, data):
3693     proutn(txt)
3694     if game.options & OPTION_CURSES:
3695         skip(1)
3696         setwnd(status_window)
3697     else:
3698         proutn(" " * (NSYM - len(txt)))
3699     proutn(data)
3700     skip(1)
3701     if game.options & OPTION_CURSES:
3702         setwnd(report_window)
3703
3704 # Code from moving.c begins here
3705
3706 def imove(icourse=None, noattack=False):
3707     "Movement execution for warp, impulse, supernova, and tractor-beam events."
3708     w = Coord()
3709
3710     def newquadrant(noattack):
3711         # Leaving quadrant -- allow final enemy attack
3712         # Don't set up attack if being pushed by nova or cloaked
3713         if len(game.enemies) != 0 and not noattack and not game.iscloaked:
3714             newcnd()
3715             for enemy in game.enemies:
3716                 finald = (w - enemy.location).distance()
3717                 enemy.kavgd = 0.5 * (finald + enemy.kdist)
3718             # Stas Sergeev added the condition
3719             # that attacks only happen if Klingons
3720             # are present and your skill is good.
3721             if game.skill > SKILL_GOOD and game.klhere > 0 and not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3722                 attack(torps_ok=False)
3723             if game.alldone:
3724                 return
3725         # check for edge of galaxy
3726         kinks = 0
3727         while True:
3728
3729
3730             kink = False
3731             if icourse.final.i < 0:
3732                 icourse.final.i = -icourse.final.i
3733                 kink = True
3734             if icourse.final.j < 0:
3735                 icourse.final.j = -icourse.final.j
3736                 kink = True
3737             if icourse.final.i >= GALSIZE*QUADSIZE:
3738                 icourse.final.i = (GALSIZE*QUADSIZE*2) - icourse.final.i
3739                 kink = True
3740             if icourse.final.j >= GALSIZE*QUADSIZE:
3741                 icourse.final.j = (GALSIZE*QUADSIZE*2) - icourse.final.j
3742                 kink = True
3743             if kink:
3744                 kinks += 1
3745             else:
3746                 break
3747         if kinks:
3748             game.nkinks += 1
3749             if game.nkinks == 3:
3750                 # Three strikes -- you're out!
3751                 finish(FNEG3)
3752                 return
3753             skip(1)
3754             prout(_("YOU HAVE ATTEMPTED TO CROSS THE NEGATIVE ENERGY BARRIER"))
3755             prout(_("AT THE EDGE OF THE GALAXY.  THE THIRD TIME YOU TRY THIS,"))
3756             prout(_("YOU WILL BE DESTROYED."))
3757         # Compute final position in new quadrant
3758         if trbeam: # Don't bother if we are to be beamed
3759             return
3760         game.quadrant = icourse.final.quadrant()
3761         game.sector = icourse.final.sector()
3762         skip(1)
3763         prout(_("Entering Quadrant %s.") % game.quadrant)
3764         game.quad[game.sector.i][game.sector.j] = game.ship
3765         newqad()
3766         if game.skill>SKILL_NOVICE:
3767             attack(torps_ok=False)
3768
3769     def check_collision(h):
3770         iquad = game.quad[h.i][h.j]
3771         if iquad != '.':
3772             # object encountered in flight path
3773             stopegy = 50.0*icourse.distance/game.optime
3774             if iquad in ('T', 'K', 'C', 'S', 'R', '?'):
3775                 for enemy in game.enemies:
3776                     if enemy.location == game.sector:
3777                         collision(rammed=False, enemy=enemy)
3778                         return True
3779                 # This should not happen
3780                 prout(_("Which way did he go?"))
3781                 return False
3782             elif iquad == ' ':
3783                 skip(1)
3784                 prouts(_("***RED ALERT!  RED ALERT!"))
3785                 skip(1)
3786                 proutn("***" + crmshp())
3787                 proutn(_(" pulled into black hole at Sector %s") % h)
3788                 # Getting pulled into a black hole was certain
3789                 # death in Almy's original.  Stas Sergeev added a
3790                 # possibility that you'll get timewarped instead.
3791                 n=0
3792                 for m in range(NDEVICES):
3793                     if game.damage[m]>0:
3794                         n += 1
3795                 probf=math.pow(1.4,(game.energy+game.shield)/5000.0-1.0)*math.pow(1.3,1.0/(n+1)-1.0)
3796                 if (game.options & OPTION_BLKHOLE) and withprob(1-probf):
3797                     timwrp()
3798                 else:
3799                     finish(FHOLE)
3800                 return True
3801             else:
3802                 # something else
3803                 skip(1)
3804                 proutn(crmshp())
3805                 if iquad == '#':
3806                     prout(_(" encounters Tholian web at %s;") % h)
3807                 else:
3808                     prout(_(" blocked by object at %s;") % h)
3809                 proutn(_("Emergency stop required "))
3810                 prout(_("%2d units of energy.") % int(stopegy))
3811                 game.energy -= stopegy
3812                 if game.energy <= 0:
3813                     finish(FNRG)
3814                 return True
3815         return False
3816
3817     trbeam = False
3818     if game.inorbit:
3819         prout(_("Helmsman Sulu- \"Leaving standard orbit.\""))
3820         game.inorbit = False
3821     # If tractor beam is to occur, don't move full distance
3822     if game.state.date+game.optime >= scheduled(FTBEAM):
3823         if game.iscloaked:
3824             # We can't be tractor beamed if cloaked,
3825             # so move the event into the future
3826             postpone(FTBEAM, game.optime + expran(1.5*game.intime/len(game.kcmdr)))
3827             pass
3828         else:
3829             trbeam = True
3830             game.condition = "red"
3831             icourse.distance = icourse.distance*(scheduled(FTBEAM)-game.state.date)/game.optime + 0.1
3832             game.optime = scheduled(FTBEAM) - game.state.date + 1e-5
3833     # Move out
3834     game.quad[game.sector.i][game.sector.j] = '.'
3835     for _m in range(icourse.moves):
3836         icourse.nexttok()
3837         w = icourse.sector()
3838         if icourse.origin.quadrant() != icourse.location.quadrant():
3839             newquadrant(noattack)
3840             break
3841         elif check_collision(w):
3842             print("Collision detected")
3843             break
3844         else:
3845             game.sector = w
3846     # We're in destination quadrant -- compute new average enemy distances
3847     game.quad[game.sector.i][game.sector.j] = game.ship
3848     if game.enemies:
3849         for enemy in game.enemies:
3850             finald = (w-enemy.location).distance()
3851             enemy.kavgd = 0.5 * (finald + enemy.kdist)
3852             enemy.kdist = finald
3853         sortenemies()
3854         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
3855             attack(torps_ok=False)
3856         for enemy in game.enemies:
3857             enemy.kavgd = enemy.kdist
3858     newcnd()
3859     drawmaps(0)
3860     setwnd(message_window)
3861     return
3862
3863 def dock(verbose):
3864     "Dock our ship at a starbase."
3865     scanner.chew()
3866     if game.condition == "docked" and verbose:
3867         prout(_("Already docked."))
3868         return
3869     if game.inorbit:
3870         prout(_("You must first leave standard orbit."))
3871         return
3872     if not game.base.is_valid() or abs(game.sector.i-game.base.i) > 1 or abs(game.sector.j-game.base.j) > 1:
3873         prout(crmshp() + _(" not adjacent to base."))
3874         return
3875     if game.iscloaked:
3876         prout(_("You cannot dock while cloaked."))
3877         return
3878     game.condition = "docked"
3879     if verbose:
3880         prout(_("Docked."))
3881     game.ididit = True
3882     if game.energy < game.inenrg:
3883         game.energy = game.inenrg
3884     game.shield = game.inshld
3885     game.torps = game.intorps
3886     game.lsupres = game.inlsr
3887     game.state.crew = FULLCREW
3888     if game.brigcapacity-game.brigfree > 0:
3889         prout(_("%d captured Klingons transferred to base") % (game.brigcapacity-game.brigfree))
3890         game.kcaptured += game.brigcapacity-game.brigfree
3891         game.brigfree = game.brigcapacity
3892     if communicating() and \
3893         ((is_scheduled(FCDBAS) or game.isatb == 1) and not game.iseenit):
3894         # get attack report from base
3895         prout(_("Lt. Uhura- \"Captain, an important message from the starbase:\""))
3896         attackreport(False)
3897         game.iseenit = True
3898
3899 def cartesian(loc1=None, loc2=None):
3900     if loc1 is None:
3901         return game.quadrant * QUADSIZE + game.sector
3902     elif loc2 is None:
3903         return game.quadrant * QUADSIZE + loc1
3904     else:
3905         return loc1 * QUADSIZE + loc2
3906
3907 def getcourse(isprobe):
3908     "Get a course and distance from the user."
3909     key = ""
3910     dquad = copy.copy(game.quadrant)
3911     navmode = "unspecified"
3912     itemp = "curt"
3913     dsect = Coord()
3914     iprompt = False
3915     if game.landed and not isprobe:
3916         prout(_("Dummy! You can't leave standard orbit until you"))
3917         proutn(_("are back aboard the ship."))
3918         scanner.chew()
3919         raise TrekError
3920     while navmode == "unspecified":
3921         if damaged(DNAVSYS):
3922             if isprobe:
3923                 prout(_("Computer damaged; manual navigation only"))
3924             else:
3925                 prout(_("Computer damaged; manual movement only"))
3926             scanner.chew()
3927             navmode = "manual"
3928             key = "IHEOL"
3929             break
3930         key = scanner.nexttok()
3931         if key == "IHEOL":
3932             proutn(_("Manual or automatic- "))
3933             iprompt = True
3934             scanner.chew()
3935         elif key == "IHALPHA":
3936             if scanner.sees("manual"):
3937                 navmode = "manual"
3938                 key = scanner.nexttok()
3939                 break
3940             elif scanner.sees("automatic"):
3941                 navmode = "automatic"
3942                 key = scanner.nexttok()
3943                 break
3944             else:
3945                 huh()
3946                 scanner.chew()
3947                 raise TrekError
3948         else: # numeric
3949             if isprobe:
3950                 prout(_("(Manual navigation assumed.)"))
3951             else:
3952                 prout(_("(Manual movement assumed.)"))
3953             navmode = "manual"
3954             break
3955     delta = Coord()
3956     if navmode == "automatic":
3957         while key == "IHEOL":
3958             if isprobe:
3959                 proutn(_("Target quadrant or quadrant&sector- "))
3960             else:
3961                 proutn(_("Destination sector or quadrant&sector- "))
3962             scanner.chew()
3963             iprompt = True
3964             key = scanner.nexttok()
3965         if key != "IHREAL":
3966             huh()
3967             raise TrekError
3968         xi = int(round(scanner.real))-1
3969         key = scanner.nexttok()
3970         if key != "IHREAL":
3971             huh()
3972             raise TrekError
3973         xj = int(round(scanner.real))-1
3974         key = scanner.nexttok()
3975         if key == "IHREAL":
3976             # both quadrant and sector specified
3977             xk = int(round(scanner.real))-1
3978             key = scanner.nexttok()
3979             if key != "IHREAL":
3980                 huh()
3981                 raise TrekError
3982             xl = int(round(scanner.real))-1
3983             dquad.i = xi
3984             dquad.j = xj
3985             dsect.i = xk
3986             dsect.j = xl
3987         else:
3988             # only one pair of numbers was specified
3989             if isprobe:
3990                 # only quadrant specified -- go to center of dest quad
3991                 dquad.i = xi
3992                 dquad.j = xj
3993                 dsect.j = dsect.i = 4        # preserves 1-origin behavior
3994             else:
3995                 # only sector specified
3996                 dsect.i = xi
3997                 dsect.j = xj
3998             itemp = "normal"
3999         if not dquad.valid_quadrant() or not dsect.valid_sector():
4000             huh()
4001             raise TrekError
4002         skip(1)
4003         if not isprobe:
4004             if itemp > "curt":
4005                 if iprompt:
4006                     prout(_("Helmsman Sulu- \"Course locked in for Sector %s.\"") % dsect)
4007             else:
4008                 prout(_("Ensign Chekov- \"Course laid in, Captain.\""))
4009         # the actual deltas get computed here
4010         delta.j = dquad.j-game.quadrant.j + (dsect.j-game.sector.j)/(QUADSIZE*1.0)
4011         delta.i = game.quadrant.i-dquad.i + (game.sector.i-dsect.i)/(QUADSIZE*1.0)
4012     else: # manual
4013         while key == "IHEOL":
4014             proutn(_("X and Y displacements- "))
4015             scanner.chew()
4016             iprompt = True
4017             key = scanner.nexttok()
4018         itemp = "verbose"
4019         if key == "IHREAL":
4020             delta.j = scanner.real
4021         else:
4022             huh()
4023             raise TrekError
4024         key = scanner.nexttok()
4025         if key == "IHREAL":
4026             delta.i = scanner.real
4027         else:
4028             delta.i = 0
4029     # Check for zero movement
4030     if delta.i == 0 and delta.j == 0:
4031         scanner.chew()
4032         raise TrekError
4033     if itemp == "verbose" and not isprobe:
4034         skip(1)
4035         prout(_("Helmsman Sulu- \"Aye, Sir.\""))
4036     scanner.chew()
4037     return course(bearing=delta.bearing(), distance=delta.distance())
4038
4039 class course:
4040     def __init__(self, bearing, distance, origin=None):
4041         self.distance = distance
4042         self.bearing = bearing
4043         if origin is None:
4044             self.origin = cartesian(game.quadrant, game.sector)
4045         else:
4046             self.origin = origin
4047         # The bearing() code we inherited from FORTRAN is actually computing
4048         # clockface directions!
4049         if self.bearing < 0.0:
4050             self.bearing += 12.0
4051         self.angle = ((15.0 - self.bearing) * 0.5235988)
4052         self.increment = Coord(-math.sin(self.angle), math.cos(self.angle))
4053         bigger = max(abs(self.increment.i), abs(self.increment.j))
4054         self.increment /= bigger
4055         self.moves = int(round(10*self.distance*bigger))
4056         self.reset()
4057         self.final = (self.location + self.moves*self.increment).roundtogrid()
4058         self.location = self.origin
4059         self.nextlocation = None
4060     def reset(self):
4061         self.location = self.origin
4062         self.step = 0
4063     def arrived(self):
4064         return self.location.roundtogrid() == self.final
4065     def nexttok(self):
4066         "Next step on course."
4067         self.step += 1
4068         self.nextlocation = self.location + self.increment
4069         samequad = (self.location.quadrant() == self.nextlocation.quadrant())
4070         self.location = self.nextlocation
4071         return samequad
4072     def quadrant(self):
4073         return self.location.quadrant()
4074     def sector(self):
4075         return self.location.sector()
4076     def power(self, w):
4077         return self.distance*(w**3)*(game.shldup+1)
4078     def time(self, w):
4079         return 10.0*self.distance/w**2
4080
4081 def impulse():
4082     "Move under impulse power."
4083     game.ididit = False
4084     if damaged(DIMPULS):
4085         scanner.chew()
4086         skip(1)
4087         prout(_("Engineer Scott- \"The impulse engines are damaged, Sir.\""))
4088         return
4089     if game.energy > 30.0:
4090         try:
4091             icourse = getcourse(isprobe=False)
4092         except TrekError:
4093             return
4094         power = 20.0 + 100.0*icourse.distance
4095     else:
4096         power = 30.0
4097     if power >= game.energy:
4098         # Insufficient power for trip
4099         skip(1)
4100         prout(_("First Officer Spock- \"Captain, the impulse engines"))
4101         prout(_("require 20.0 units to engage, plus 100.0 units per"))
4102         if game.energy > 30:
4103             proutn(_("quadrant.  We can go, therefore, a maximum of %d") %
4104                    int(0.01 * (game.energy-20.0)-0.05))
4105             prout(_(" quadrants.\""))
4106         else:
4107             prout(_("quadrant.  They are, therefore, useless.\""))
4108         scanner.chew()
4109         return
4110     # Make sure enough time is left for the trip
4111     game.optime = icourse.distance/0.095
4112     if game.optime >= game.state.remtime:
4113         prout(_("First Officer Spock- \"Captain, our speed under impulse"))
4114         prout(_("power is only 0.95 sectors per stardate. Are you sure"))
4115         proutn(_("we dare spend the time?\" "))
4116         if not ja():
4117             return
4118     # Activate impulse engines and pay the cost
4119     imove(icourse, noattack=False)
4120     game.ididit = True
4121     if game.alldone:
4122         return
4123     power = 20.0 + 100.0*icourse.distance
4124     game.energy -= power
4125     game.optime = icourse.distance/0.095
4126     if game.energy <= 0:
4127         finish(FNRG)
4128     return
4129
4130 def warp(wcourse, involuntary):
4131     "ove under warp drive."
4132     blooey = False; twarp = False
4133     if not involuntary: # Not WARPX entry
4134         game.ididit = False
4135         if game.iscloaked:
4136             scanner.chew()
4137             skip(1)
4138             prout(_("Engineer Scott- \"The warp engines can not be used while cloaked, Sir.\""))
4139             return
4140         if game.damage[DWARPEN] > 10.0:
4141             scanner.chew()
4142             skip(1)
4143             prout(_("Engineer Scott- \"The warp engines are damaged, Sir.\""))
4144             return
4145         if damaged(DWARPEN) and game.warpfac > 4.0:
4146             scanner.chew()
4147             skip(1)
4148             prout(_("Engineer Scott- \"Sorry, Captain. Until this damage"))
4149             prout(_("  is repaired, I can only give you warp 4.\""))
4150             return
4151                # Read in course and distance
4152         if wcourse is None:
4153             try:
4154                 wcourse = getcourse(isprobe=False)
4155             except TrekError:
4156                 return
4157         # Make sure starship has enough energy for the trip
4158         # Note: this formula is slightly different from the C version,
4159         # and lets you skate a bit closer to the edge.
4160         if wcourse.power(game.warpfac) >= game.energy:
4161             # Insufficient power for trip
4162             game.ididit = False
4163             skip(1)
4164             prout(_("Engineering to bridge--"))
4165             if not game.shldup or 0.5*wcourse.power(game.warpfac) > game.energy:
4166                 iwarp = (game.energy/(wcourse.distance+0.05)) ** 0.333333333
4167                 if iwarp <= 0:
4168                     prout(_("We can't do it, Captain. We don't have enough energy."))
4169                 else:
4170                     proutn(_("We don't have enough energy, but we could do it at warp %d") % iwarp)
4171                     if game.shldup:
4172                         prout(",")
4173                         prout(_("if you'll lower the shields."))
4174                     else:
4175                         prout(".")
4176             else:
4177                 prout(_("We haven't the energy to go that far with the shields up."))
4178             return
4179         # Make sure enough time is left for the trip
4180         game.optime = wcourse.time(game.warpfac)
4181         if game.optime >= 0.8*game.state.remtime:
4182             skip(1)
4183             prout(_("First Officer Spock- \"Captain, I compute that such"))
4184             proutn(_("  a trip would require approximately %2.0f") %
4185                    (100.0*game.optime/game.state.remtime))
4186             prout(_(" percent of our"))
4187             proutn(_("  remaining time.  Are you sure this is wise?\" "))
4188             if not ja():
4189                 game.ididit = False
4190                 game.optime=0
4191                 return
4192     # Entry WARPX
4193     if game.warpfac > 6.0:
4194         # Decide if engine damage will occur
4195         # ESR: Seems wrong. Probability of damage goes *down* with distance?
4196         prob = wcourse.distance*(6.0-game.warpfac)**2/66.666666666
4197         if prob > randreal():
4198             blooey = True
4199             wcourse.distance = randreal(wcourse.distance)
4200         # Decide if time warp will occur
4201         if 0.5*wcourse.distance*math.pow(7.0,game.warpfac-10.0) > randreal():
4202             twarp = True
4203         if game.idebug and game.warpfac==10 and not twarp:
4204             blooey = False
4205             proutn("=== Force time warp? ")
4206             if ja():
4207                 twarp = True
4208         if blooey or twarp:
4209             # If time warp or engine damage, check path
4210             # If it is obstructed, don't do warp or damage
4211             look = wcourse.moves
4212             while look > 0:
4213                 look -= 1
4214                 wcourse.nexttok()
4215                 w = wcourse.sector()
4216                 if not w.valid_sector():
4217                     break
4218                 if game.quad[w.i][w.j] != '.':
4219                     blooey = False
4220                     twarp = False
4221             wcourse.reset()
4222     # Activate Warp Engines and pay the cost
4223     imove(wcourse, noattack=False)
4224     if game.alldone:
4225         return
4226     game.energy -= wcourse.power(game.warpfac)
4227     if game.energy <= 0:
4228         finish(FNRG)
4229     game.optime = wcourse.time(game.warpfac)
4230     if twarp:
4231         timwrp()
4232     if blooey:
4233         game.damage[DWARPEN] = game.damfac * randreal(1.0, 4.0)
4234         skip(1)
4235         prout(_("Engineering to bridge--"))
4236         prout(_("  Scott here.  The warp engines are damaged."))
4237         prout(_("  We'll have to reduce speed to warp 4."))
4238     game.ididit = True
4239     return
4240
4241 def setwarp():
4242     "Change the warp factor."
4243     while True:
4244         key=scanner.nexttok()
4245         if key != "IHEOL":
4246             break
4247         scanner.chew()
4248         proutn(_("Warp factor- "))
4249     if key != "IHREAL":
4250         huh()
4251         return
4252     if game.damage[DWARPEN] > 10.0:
4253         prout(_("Warp engines inoperative."))
4254         return
4255     if damaged(DWARPEN) and scanner.real > 4.0:
4256         prout(_("Engineer Scott- \"I'm doing my best, Captain,"))
4257         prout(_("  but right now we can only go warp 4.\""))
4258         return
4259     if scanner.real > 10.0:
4260         prout(_("Helmsman Sulu- \"Our top speed is warp 10, Captain.\""))
4261         return
4262     if scanner.real < 1.0:
4263         prout(_("Helmsman Sulu- \"We can't go below warp 1, Captain.\""))
4264         return
4265     oldfac = game.warpfac
4266     game.warpfac = scanner.real
4267     if game.warpfac <= oldfac or game.warpfac <= 6.0:
4268         prout(_("Helmsman Sulu- \"Warp factor %d, Captain.\"") %
4269               int(game.warpfac))
4270         return
4271     if game.warpfac < 8.00:
4272         prout(_("Engineer Scott- \"Aye, but our maximum safe speed is warp 6.\""))
4273         return
4274     if game.warpfac == 10.0:
4275         prout(_("Engineer Scott- \"Aye, Captain, we'll try it.\""))
4276         return
4277     prout(_("Engineer Scott- \"Aye, Captain, but our engines may not take it.\""))
4278     return
4279
4280 def atover(igrab):
4281     "Cope with being tossed out of quadrant by supernova or yanked by beam."
4282     scanner.chew()
4283     # is captain on planet?
4284     if game.landed:
4285         if damaged(DTRANSP):
4286             finish(FPNOVA)
4287             return
4288         prout(_("Scotty rushes to the transporter controls."))
4289         if game.shldup:
4290             prout(_("But with the shields up it's hopeless."))
4291             finish(FPNOVA)
4292         prouts(_("His desperate attempt to rescue you . . ."))
4293         if withprob(0.5):
4294             prout(_("fails."))
4295             finish(FPNOVA)
4296             return
4297         prout(_("SUCCEEDS!"))
4298         if game.imine:
4299             game.imine = False
4300             proutn(_("The crystals mined were "))
4301             if withprob(0.25):
4302                 prout(_("lost."))
4303             else:
4304                 prout(_("saved."))
4305                 game.icrystl = True
4306     if igrab:
4307         return
4308     # Check to see if captain in shuttle craft
4309     if game.icraft:
4310         finish(FSTRACTOR)
4311     if game.alldone:
4312         return
4313     # Inform captain of attempt to reach safety
4314     skip(1)
4315     while True:
4316         if game.justin:
4317             prouts(_("***RED ALERT!  RED ALERT!"))
4318             skip(1)
4319             proutn(_("The %s has stopped in a quadrant containing") % crmshp())
4320             prouts(_("   a supernova."))
4321             skip(2)
4322         prout(_("***Emergency automatic override attempts to hurl ")+crmshp())
4323         prout(_("safely out of quadrant."))
4324         if not damaged(DRADIO):
4325             game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
4326         # Try to use warp engines
4327         if damaged(DWARPEN):
4328             skip(1)
4329             prout(_("Warp engines damaged."))
4330             finish(FSNOVAED)
4331             return
4332         game.warpfac = randreal(6.0, 8.0)
4333         prout(_("Warp factor set to %d") % int(game.warpfac))
4334         power = 0.75*game.energy
4335         dist = power/(game.warpfac*game.warpfac*game.warpfac*(game.shldup+1))
4336         dist = max(dist, randreal(math.sqrt(2)))
4337         bugout = course(bearing=randreal(12), distance=dist)        # How dumb!
4338         game.optime = bugout.time(game.warpfac)
4339         game.justin = False
4340         game.inorbit = False
4341         warp(bugout, involuntary=True)
4342         if not game.justin:
4343             # This is bad news, we didn't leave quadrant.
4344             if game.alldone:
4345                 return
4346             skip(1)
4347             prout(_("Insufficient energy to leave quadrant."))
4348             finish(FSNOVAED)
4349             return
4350         # Repeat if another snova
4351         if not game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
4352             break
4353     if game.unwon()==0:
4354         finish(FWON) # Snova killed remaining enemy.
4355
4356 def timwrp():
4357     "Let's do the time warp again."
4358     prout(_("***TIME WARP ENTERED."))
4359     if game.state.snap and withprob(0.5):
4360         # Go back in time
4361         prout(_("You are traveling backwards in time %d stardates.") %
4362               int(game.state.date-game.snapsht.date))
4363         game.state = game.snapsht
4364         game.state.snap = False
4365         if len(game.state.kcmdr):
4366             schedule(FTBEAM, expran(game.intime/len(game.state.kcmdr)))
4367             schedule(FBATTAK, expran(0.3*game.intime))
4368         schedule(FSNOVA, expran(0.5*game.intime))
4369         # next snapshot will be sooner
4370         schedule(FSNAP, expran(0.25*game.state.remtime))
4371
4372         if game.state.nscrem:
4373             schedule(FSCMOVE, 0.2777)
4374         game.isatb = 0
4375         unschedule(FCDBAS)
4376         unschedule(FSCDBAS)
4377         game.battle.invalidate()
4378         # Make sure Galileo is consistant -- Snapshot may have been taken
4379         # when on planet, which would give us two Galileos!
4380         gotit = False
4381         for l in range(game.inplan):
4382             if game.state.planets[l].known == "shuttle_down":
4383                 gotit = True
4384                 if game.iscraft == "onship" and game.ship=='E':
4385                     prout(_("Chekov-  \"Security reports the Galileo has disappeared, Sir!"))
4386                     game.iscraft = "offship"
4387         # Likewise, if in the original time the Galileo was abandoned, but
4388         # was on ship earlier, it would have vanished -- let's restore it.
4389         if game.iscraft == "offship" and not gotit and game.damage[DSHUTTL] >= 0.0:
4390             prout(_("Chekov-  \"Security reports the Galileo has reappeared in the dock!\""))
4391             game.iscraft = "onship"
4392         # There used to be code to do the actual reconstrction here,
4393         # but the starchart is now part of the snapshotted galaxy state.
4394         prout(_("Spock has reconstructed a correct star chart from memory"))
4395     else:
4396         # Go forward in time
4397         game.optime = expran(0.5*game.intime)
4398         prout(_("You are traveling forward in time %d stardates.") % int(game.optime))
4399         # cheat to make sure no tractor beams occur during time warp
4400         postpone(FTBEAM, game.optime)
4401         game.damage[DRADIO] += game.optime
4402     newqad()
4403     events()        # Stas Sergeev added this -- do pending events
4404
4405 def probe():
4406     "Launch deep-space probe."
4407     # New code to launch a deep space probe
4408     if game.nprobes == 0:
4409         scanner.chew()
4410         skip(1)
4411         if game.ship == 'E':
4412             prout(_("Engineer Scott- \"We have no more deep space probes, Sir.\""))
4413         else:
4414             prout(_("Ye Faerie Queene has no deep space probes."))
4415         return
4416     if damaged(DDSP):
4417         scanner.chew()
4418         skip(1)
4419         prout(_("Engineer Scott- \"The probe launcher is damaged, Sir.\""))
4420         return
4421     if is_scheduled(FDSPROB):
4422         scanner.chew()
4423         skip(1)
4424         if damaged(DRADIO) and game.condition != "docked":
4425             prout(_("Spock-  \"Records show the previous probe has not yet"))
4426             prout(_("   reached its destination.\""))
4427         else:
4428             prout(_("Uhura- \"The previous probe is still reporting data, Sir.\""))
4429         return
4430     key = scanner.nexttok()
4431     if key == "IHEOL":
4432         if game.nprobes == 1:
4433             prout(_("1 probe left."))
4434         else:
4435             prout(_("%d probes left") % game.nprobes)
4436         proutn(_("Are you sure you want to fire a probe? "))
4437         if not ja():
4438             return
4439     game.isarmed = False
4440     if key == "IHALPHA" and scanner.token == "armed":
4441         game.isarmed = True
4442         key = scanner.nexttok()
4443     elif key == "IHEOL":
4444         proutn(_("Arm NOVAMAX warhead? "))
4445         game.isarmed = ja()
4446     elif key == "IHREAL":                # first element of course
4447         scanner.push(scanner.token)
4448     try:
4449         game.probe = getcourse(isprobe=True)
4450     except TrekError:
4451         return
4452     game.nprobes -= 1
4453     schedule(FDSPROB, 0.01) # Time to move one sector
4454     prout(_("Ensign Chekov-  \"The deep space probe is launched, Captain.\""))
4455     game.ididit = True
4456     return
4457
4458 def mayday():
4459     "Yell for help from nearest starbase."
4460     # There's more than one way to move in this game!
4461     scanner.chew()
4462     # Test for conditions which prevent calling for help
4463     if game.condition == "docked":
4464         prout(_("Lt. Uhura-  \"But Captain, we're already docked.\""))
4465         return
4466     if damaged(DRADIO):
4467         prout(_("Subspace radio damaged."))
4468         return
4469     if not game.state.baseq:
4470         prout(_("Lt. Uhura-  \"Captain, I'm not getting any response from Starbase.\""))
4471         return
4472     if game.landed:
4473         prout(_("You must be aboard the %s.") % crmshp())
4474         return
4475     # OK -- call for help from nearest starbase
4476     game.nhelp += 1
4477     if game.base.i!=0:
4478         # There's one in this quadrant
4479         ddist = (game.base - game.sector).distance()
4480     else:
4481         ibq = None      # Force base-quadrant game to persist past loop
4482         ddist = FOREVER
4483         for ibq in game.state.baseq:
4484             xdist = QUADSIZE * (ibq - game.quadrant).distance()
4485             if xdist < ddist:
4486                 ddist = xdist
4487         if ibq is None:
4488             prout(_("No starbases remain. You are alone in a hostile galaxy."))
4489             return
4490         # Since starbase not in quadrant, set up new quadrant
4491         game.quadrant = ibq
4492         newqad()
4493     # dematerialize starship
4494     game.quad[game.sector.i][game.sector.j]='.'
4495     proutn(_("Starbase in Quadrant %s responds--%s dematerializes") \
4496            % (game.quadrant, crmshp()))
4497     game.sector.invalidate()
4498     for m in range(1, 5+1):
4499         w = game.base.scatter()
4500         if w.valid_sector() and game.quad[w.i][w.j]=='.':
4501             # found one -- finish up
4502             game.sector = w
4503             break
4504     if not game.sector.is_valid():
4505         prout(_("You have been lost in space..."))
4506         finish(FMATERIALIZE)
4507         return
4508     # Give starbase three chances to rematerialize starship
4509     probf = math.pow((1.0 - math.pow(0.98,ddist)), 0.33333333)
4510     for m in range(1, 3+1):
4511         if m == 1: proutn(_("1st"))
4512         elif m == 2: proutn(_("2nd"))
4513         elif m == 3: proutn(_("3rd"))
4514         proutn(_(" attempt to re-materialize ") + crmshp())
4515         game.quad[game.sector.i][game.sector.j]=('-','o','O')[m-1]
4516         textcolor(RED)
4517         warble()
4518         if randreal() > probf:
4519             break
4520         prout(_("fails."))
4521         textcolor(DEFAULT)
4522         curses.delay_output(500)
4523     if m > 3:
4524         game.quad[game.sector.i][game.sector.j]='?'
4525         game.alive = False
4526         drawmaps(1)
4527         setwnd(message_window)
4528         finish(FMATERIALIZE)
4529         return
4530     game.quad[game.sector.i][game.sector.j]=game.ship
4531     textcolor(GREEN)
4532     prout(_("succeeds."))
4533     textcolor(DEFAULT)
4534     dock(False)
4535     skip(1)
4536     prout(_("Lt. Uhura-  \"Captain, we made it!\""))
4537
4538 def abandon():
4539     "Abandon ship."
4540     scanner.chew()
4541     if game.condition=="docked":
4542         if game.ship!='E':
4543             prout(_("You cannot abandon Ye Faerie Queene."))
4544             return
4545     else:
4546         # Must take shuttle craft to exit
4547         if game.damage[DSHUTTL]==-1:
4548             prout(_("Ye Faerie Queene has no shuttle craft."))
4549             return
4550         if game.damage[DSHUTTL]<0:
4551             prout(_("Shuttle craft now serving Big Macs."))
4552             return
4553         if game.damage[DSHUTTL]>0:
4554             prout(_("Shuttle craft damaged."))
4555             return
4556         if game.landed:
4557             prout(_("You must be aboard the ship."))
4558             return
4559         if game.iscraft != "onship":
4560             prout(_("Shuttle craft not currently available."))
4561             return
4562         # Emit abandon ship messages
4563         skip(1)
4564         prouts(_("***ABANDON SHIP!  ABANDON SHIP!"))
4565         skip(1)
4566         prouts(_("***ALL HANDS ABANDON SHIP!"))
4567         skip(2)
4568         prout(_("Captain and crew escape in shuttle craft."))
4569         if not game.state.baseq:
4570             # Oops! no place to go...
4571             finish(FABANDN)
4572             return
4573         q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
4574         # Dispose of crew
4575         if not (game.options & OPTION_WORLDS) and not damaged(DTRANSP):
4576             prout(_("Remainder of ship's complement beam down"))
4577             prout(_("to nearest habitable planet."))
4578         elif q.planet != None and not damaged(DTRANSP):
4579             prout(_("Remainder of ship's complement beam down to %s.") %
4580                   q.planet)
4581         else:
4582             prout(_("Entire crew of %d left to die in outer space.") %
4583                   game.state.crew)
4584             game.casual += game.state.crew
4585             game.abandoned += game.state.crew
4586         # If at least one base left, give 'em the Faerie Queene
4587         skip(1)
4588         game.icrystl = False # crystals are lost
4589         game.nprobes = 0 # No probes
4590         prout(_("You are captured by Klingons and released to"))
4591         prout(_("the Federation in a prisoner-of-war exchange."))
4592         nb = randrange(len(game.state.baseq))
4593         # Set up quadrant and position FQ adjacient to base
4594         if not game.quadrant == game.state.baseq[nb]:
4595             game.quadrant = game.state.baseq[nb]
4596             game.sector.i = game.sector.j = 5
4597             newqad()
4598         while True:
4599             # position next to base by trial and error
4600             game.quad[game.sector.i][game.sector.j] = '.'
4601             l = QUADSIZE
4602             for l in range(QUADSIZE):
4603                 game.sector = game.base.scatter()
4604                 if game.sector.valid_sector() and \
4605                        game.quad[game.sector.i][game.sector.j] == '.':
4606                     break
4607             if l < QUADSIZE:
4608                 break # found a spot
4609             game.sector.i=QUADSIZE/2
4610             game.sector.j=QUADSIZE/2
4611             newqad()
4612     # Get new commission
4613     game.quad[game.sector.i][game.sector.j] = game.ship = 'F'
4614     game.state.crew = FULLCREW
4615     prout(_("Starfleet puts you in command of another ship,"))
4616     prout(_("the Faerie Queene, which is antiquated but,"))
4617     prout(_("still useable."))
4618     if game.icrystl:
4619         prout(_("The dilithium crystals have been moved."))
4620     game.imine = False
4621     game.iscraft = "offship" # Galileo disappears
4622     # Resupply ship
4623     game.condition="docked"
4624     for l in range(NDEVICES):
4625         game.damage[l] = 0.0
4626     game.damage[DSHUTTL] = -1
4627     game.energy = game.inenrg = 3000.0
4628     game.shield = game.inshld = 1250.0
4629     game.torps = game.intorps = 6
4630     game.lsupres=game.inlsr=3.0
4631     game.shldup=False
4632     game.warpfac=5.0
4633     game.brigfree = game.brigcapacity = 300
4634     return
4635
4636 # Code from planets.c begins here.
4637
4638 def consumeTime():
4639     "Abort a lengthy operation if an event interrupts it."
4640     game.ididit = True
4641     events()
4642     if game.alldone or game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova or game.justin:
4643         return True
4644     return False
4645
4646 def survey():
4647     "Report on (uninhabited) planets in the galaxy."
4648     iknow = False
4649     skip(1)
4650     scanner.chew()
4651     prout(_("Spock-  \"Planet report follows, Captain.\""))
4652     skip(1)
4653     for i in range(game.inplan):
4654         if game.state.planets[i].pclass == "destroyed":
4655             continue
4656         if (game.state.planets[i].known != "unknown" \
4657             and not game.state.planets[i].inhabited) \
4658             or game.idebug:
4659             iknow = True
4660             if game.idebug and game.state.planets[i].known=="unknown":
4661                 proutn("(Unknown) ")
4662             proutn(_("Quadrant %s") % game.state.planets[i].quadrant)
4663             proutn(_("   class "))
4664             proutn(game.state.planets[i].pclass)
4665             proutn("   ")
4666             if game.state.planets[i].crystals != "present":
4667                 proutn(_("no "))
4668             prout(_("dilithium crystals present."))
4669             if game.state.planets[i].known=="shuttle_down":
4670                 prout(_("    Shuttle Craft Galileo on surface."))
4671     if not iknow:
4672         prout(_("No information available."))
4673
4674 def orbit():
4675     "Enter standard orbit."
4676     skip(1)
4677     scanner.chew()
4678     if game.inorbit:
4679         prout(_("Already in standard orbit."))
4680         return
4681     if damaged(DWARPEN) and damaged(DIMPULS):
4682         prout(_("Both warp and impulse engines damaged."))
4683         return
4684     if not game.plnet.is_valid():
4685         prout("There is no planet in this sector.")
4686         return
4687     if abs(game.sector.i-game.plnet.i)>1 or abs(game.sector.j-game.plnet.j)>1:
4688         prout(crmshp() + _(" not adjacent to planet."))
4689         skip(1)
4690         return
4691     game.optime = randreal(0.02, 0.05)
4692     prout(_("Helmsman Sulu-  \"Entering standard orbit, Sir.\""))
4693     newcnd()
4694     if consumeTime():
4695         return
4696     game.height = randreal(1400, 8600)
4697     prout(_("Sulu-  \"Entered orbit at altitude %.2f kilometers.\"") % game.height)
4698     game.inorbit = True
4699     game.ididit = True
4700
4701 def sensor():
4702     "Examine planets in this quadrant."
4703     if damaged(DSRSENS):
4704         if game.options & OPTION_TTY:
4705             prout(_("Short range sensors damaged."))
4706         return
4707     if game.iplnet is None:
4708         if game.options & OPTION_TTY:
4709             prout(_("Spock- \"No planet in this quadrant, Captain.\""))
4710         return
4711     if game.iplnet.known == "unknown":
4712         prout(_("Spock-  \"Sensor scan for Quadrant %s-") % game.quadrant)
4713         skip(1)
4714         prout(_("         Planet at Sector %s is of class %s.") %
4715               (game.plnet, game.iplnet.pclass))
4716         if game.iplnet.known=="shuttle_down":
4717             prout(_("         Sensors show Galileo still on surface."))
4718         proutn(_("         Readings indicate"))
4719         if game.iplnet.crystals != "present":
4720             proutn(_(" no"))
4721         prout(_(" dilithium crystals present.\""))
4722         if game.iplnet.known == "unknown":
4723             game.iplnet.known = "known"
4724     elif game.iplnet.inhabited:
4725         prout(_("Spock-  \"The inhabited planet %s ") % game.iplnet.name)
4726         prout(_("        is located at Sector %s, Captain.\"") % game.plnet)
4727
4728 def beam():
4729     "Use the transporter."
4730     nrgneed = 0
4731     scanner.chew()
4732     skip(1)
4733     if damaged(DTRANSP):
4734         prout(_("Transporter damaged."))
4735         if not damaged(DSHUTTL) and (game.iplnet.known=="shuttle_down" or game.iscraft == "onship"):
4736             skip(1)
4737             proutn(_("Spock-  \"May I suggest the shuttle craft, Sir?\" "))
4738             if ja():
4739                 shuttle()
4740         return
4741     if not game.inorbit:
4742         prout(crmshp() + _(" not in standard orbit."))
4743         return
4744     if game.shldup:
4745         prout(_("Impossible to transport through shields."))
4746         return
4747     if game.iplnet.known=="unknown":
4748         prout(_("Spock-  \"Captain, we have no information on this planet"))
4749         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4750         prout(_("  you may not go down.\""))
4751         return
4752     if not game.landed and game.iplnet.crystals=="absent":
4753         prout(_("Spock-  \"Captain, I fail to see the logic in"))
4754         prout(_("  exploring a planet with no dilithium crystals."))
4755         proutn(_("  Are you sure this is wise?\" "))
4756         if not ja():
4757             scanner.chew()
4758             return
4759     if not (game.options & OPTION_PLAIN):
4760         nrgneed = 50 * game.skill + game.height / 100.0
4761         if nrgneed > game.energy:
4762             prout(_("Engineering to bridge--"))
4763             prout(_("  Captain, we don't have enough energy for transportation."))
4764             return
4765         if not game.landed and nrgneed * 2 > game.energy:
4766             prout(_("Engineering to bridge--"))
4767             prout(_("  Captain, we have enough energy only to transport you down to"))
4768             prout(_("  the planet, but there wouldn't be an energy for the trip back."))
4769             if game.iplnet.known == "shuttle_down":
4770                 prout(_("  Although the Galileo shuttle craft may still be on a surface."))
4771             proutn(_("  Are you sure this is wise?\" "))
4772             if not ja():
4773                 scanner.chew()
4774                 return
4775     if game.landed:
4776         # Coming from planet
4777         if game.iplnet.known=="shuttle_down":
4778             proutn(_("Spock-  \"Wouldn't you rather take the Galileo?\" "))
4779             if ja():
4780                 scanner.chew()
4781                 return
4782             prout(_("Your crew hides the Galileo to prevent capture by aliens."))
4783         prout(_("Landing party assembled, ready to beam up."))
4784         skip(1)
4785         prout(_("Kirk whips out communicator..."))
4786         prouts(_("BEEP  BEEP  BEEP"))
4787         skip(2)
4788         prout(_("\"Kirk to enterprise-  Lock on coordinates...energize.\""))
4789     else:
4790         # Going to planet
4791         prout(_("Scotty-  \"Transporter room ready, Sir.\""))
4792         skip(1)
4793         prout(_("Kirk and landing party prepare to beam down to planet surface."))
4794         skip(1)
4795         prout(_("Kirk-  \"Energize.\""))
4796     game.ididit = True
4797     skip(1)
4798     prouts("WWHOOOIIIIIRRRRREEEE.E.E.  .  .  .  .   .    .")
4799     skip(2)
4800     if not withprob(0.98):
4801         prouts("BOOOIIIOOOIIOOOOIIIOIING . . .")
4802         skip(2)
4803         prout(_("Scotty-  \"Oh my God!  I've lost them.\""))
4804         finish(FLOST)
4805         return
4806     prouts(".    .   .  .  .  .  .E.E.EEEERRRRRIIIIIOOOHWW")
4807     game.landed = not game.landed
4808     game.energy -= nrgneed
4809     skip(2)
4810     prout(_("Transport complete."))
4811     if game.landed and game.iplnet.known=="shuttle_down":
4812         prout(_("The shuttle craft Galileo is here!"))
4813     if not game.landed and game.imine:
4814         game.icrystl = True
4815         game.cryprob = 0.05
4816     game.imine = False
4817     return
4818
4819 def mine():
4820     "Strip-mine a world for dilithium."
4821     skip(1)
4822     scanner.chew()
4823     if not game.landed:
4824         prout(_("Mining party not on planet."))
4825         return
4826     if game.iplnet.crystals == "mined":
4827         prout(_("This planet has already been strip-mined for dilithium."))
4828         return
4829     elif game.iplnet.crystals == "absent":
4830         prout(_("No dilithium crystals on this planet."))
4831         return
4832     if game.imine:
4833         prout(_("You've already mined enough crystals for this trip."))
4834         return
4835     if game.icrystl and game.cryprob == 0.05:
4836         prout(_("With all those fresh crystals aboard the ") + crmshp())
4837         prout(_("there's no reason to mine more at this time."))
4838         return
4839     game.optime = randreal(0.1, 0.3)*(ord(game.iplnet.pclass)-ord("L"))
4840     if consumeTime():
4841         return
4842     prout(_("Mining operation complete."))
4843     game.iplnet.crystals = "mined"
4844     game.imine = game.ididit = True
4845
4846 def usecrystals():
4847     "Use dilithium crystals."
4848     game.ididit = False
4849     skip(1)
4850     scanner.chew()
4851     if not game.icrystl:
4852         prout(_("No dilithium crystals available."))
4853         return
4854     if game.energy >= 1000:
4855         prout(_("Spock-  \"Captain, Starfleet Regulations prohibit such an operation"))
4856         prout(_("  except when Condition Yellow exists."))
4857         return
4858     prout(_("Spock- \"Captain, I must warn you that loading"))
4859     prout(_("  raw dilithium crystals into the ship's power"))
4860     prout(_("  system may risk a severe explosion."))
4861     proutn(_("  Are you sure this is wise?\" "))
4862     if not ja():
4863         scanner.chew()
4864         return
4865     skip(1)
4866     prout(_("Engineering Officer Scott-  \"(GULP) Aye Sir."))
4867     prout(_("  Mr. Spock and I will try it.\""))
4868     skip(1)
4869     prout(_("Spock-  \"Crystals in place, Sir."))
4870     prout(_("  Ready to activate circuit.\""))
4871     skip(1)
4872     prouts(_("Scotty-  \"Keep your fingers crossed, Sir!\""))
4873     skip(1)
4874     if withprob(game.cryprob):
4875         prouts(_("  \"Activating now! - - No good!  It's***"))
4876         skip(2)
4877         prouts(_("***RED ALERT!  RED A*L********************************"))
4878         skip(1)
4879         stars()
4880         prouts(_("******************   KA-BOOM!!!!   *******************"))
4881         skip(1)
4882         kaboom()
4883         return
4884     game.energy += randreal(5000.0, 5500.0)
4885     prouts(_("  \"Activating now! - - "))
4886     prout(_("The instruments"))
4887     prout(_("   are going crazy, but I think it's"))
4888     prout(_("   going to work!!  Congratulations, Sir!\""))
4889     game.cryprob *= 2.0
4890     game.ididit = True
4891
4892 def shuttle():
4893     "Use shuttlecraft for planetary jaunt."
4894     scanner.chew()
4895     skip(1)
4896     if damaged(DSHUTTL):
4897         if game.damage[DSHUTTL] == -1.0:
4898             if game.inorbit and game.iplnet.known == "shuttle_down":
4899                 prout(_("Ye Faerie Queene has no shuttle craft bay to dock it at."))
4900             else:
4901                 prout(_("Ye Faerie Queene had no shuttle craft."))
4902         elif game.damage[DSHUTTL] > 0:
4903             prout(_("The Galileo is damaged."))
4904         else: # game.damage[DSHUTTL] < 0
4905             prout(_("Shuttle craft is now serving Big Macs."))
4906         return
4907     if not game.inorbit:
4908         prout(crmshp() + _(" not in standard orbit."))
4909         return
4910     if (game.iplnet.known != "shuttle_down") and game.iscraft != "onship":
4911         prout(_("Shuttle craft not currently available."))
4912         return
4913     if not game.landed and game.iplnet.known=="shuttle_down":
4914         prout(_("You will have to beam down to retrieve the shuttle craft."))
4915         return
4916     if game.shldup or game.condition == "docked":
4917         prout(_("Shuttle craft cannot pass through shields."))
4918         return
4919     if game.iplnet.known=="unknown":
4920         prout(_("Spock-  \"Captain, we have no information on this planet"))
4921         prout(_("  and Starfleet Regulations clearly state that in this situation"))
4922         prout(_("  you may not fly down.\""))
4923         return
4924     game.optime = 3.0e-5*game.height
4925     if game.optime >= 0.8*game.state.remtime:
4926         prout(_("First Officer Spock-  \"Captain, I compute that such"))
4927         proutn(_("  a maneuver would require approximately %2d%% of our") % \
4928                int(100*game.optime/game.state.remtime))
4929         prout(_("remaining time."))
4930         proutn(_("Are you sure this is wise?\" "))
4931         if not ja():
4932             game.optime = 0.0
4933             return
4934     if game.landed:
4935         # Kirk on planet
4936         if game.iscraft == "onship":
4937             # Galileo on ship!
4938             if not damaged(DTRANSP):
4939                 proutn(_("Spock-  \"Would you rather use the transporter?\" "))
4940                 if ja():
4941                     beam()
4942                     return
4943                 proutn(_("Shuttle crew"))
4944             else:
4945                 proutn(_("Rescue party"))
4946             prout(_(" boards Galileo and swoops toward planet surface."))
4947             game.iscraft = "offship"
4948             skip(1)
4949             if consumeTime():
4950                 return
4951             game.iplnet.known="shuttle_down"
4952             prout(_("Trip complete."))
4953             return
4954         else:
4955             # Ready to go back to ship
4956             prout(_("You and your mining party board the"))
4957             prout(_("shuttle craft for the trip back to the Enterprise."))
4958             skip(1)
4959             prouts(_("The short hop begins . . ."))
4960             skip(1)
4961             game.iplnet.known="known"
4962             game.icraft = True
4963             skip(1)
4964             game.landed = False
4965             if consumeTime():
4966                 return
4967             game.iscraft = "onship"
4968             game.icraft = False
4969             if game.imine:
4970                 game.icrystl = True
4971                 game.cryprob = 0.05
4972             game.imine = False
4973             prout(_("Trip complete."))
4974             return
4975     else:
4976         # Kirk on ship and so is Galileo
4977         prout(_("Mining party assembles in the hangar deck,"))
4978         prout(_("ready to board the shuttle craft \"Galileo\"."))
4979         skip(1)
4980         prouts(_("The hangar doors open; the trip begins."))
4981         skip(1)
4982         game.icraft = True
4983         game.iscraft = "offship"
4984         if consumeTime():
4985             return
4986         game.iplnet.known = "shuttle_down"
4987         game.landed = True
4988         game.icraft = False
4989         prout(_("Trip complete."))
4990         return
4991
4992 def deathray():
4993     "Use the big zapper."
4994     game.ididit = False
4995     skip(1)
4996     scanner.chew()
4997     if game.ship != 'E':
4998         prout(_("Ye Faerie Queene has no death ray."))
4999         return
5000     if len(game.enemies)==0:
5001         prout(_("Sulu-  \"But Sir, there are no enemies in this quadrant.\""))
5002         return
5003     if damaged(DDRAY):
5004         prout(_("Death Ray is damaged."))
5005         return
5006     prout(_("Spock-  \"Captain, the 'Experimental Death Ray'"))
5007     prout(_("  is highly unpredictible.  Considering the alternatives,"))
5008     proutn(_("  are you sure this is wise?\" "))
5009     if not ja():
5010         return
5011     prout(_("Spock-  \"Acknowledged.\""))
5012     skip(1)
5013     game.ididit = True
5014     prouts(_("WHOOEE ... WHOOEE ... WHOOEE ... WHOOEE"))
5015     skip(1)
5016     prout(_("Crew scrambles in emergency preparation."))
5017     prout(_("Spock and Scotty ready the death ray and"))
5018     prout(_("prepare to channel all ship's power to the device."))
5019     skip(1)
5020     prout(_("Spock-  \"Preparations complete, sir.\""))
5021     prout(_("Kirk-  \"Engage!\""))
5022     skip(1)
5023     prouts(_("WHIRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"))
5024     skip(1)
5025     dprob = 0.30
5026     if game.options & OPTION_PLAIN:
5027         dprob = 0.5
5028     r = randreal()
5029     if r > dprob:
5030         prouts(_("Sulu- \"Captain!  It's working!\""))
5031         skip(2)
5032         while len(game.enemies) > 0:
5033             deadkl(game.enemies[1].location, game.quad[game.enemies[1].location.i][game.enemies[1].location.j],game.enemies[1].location)
5034         prout(_("Ensign Chekov-  \"Congratulations, Captain!\""))
5035         if game.unwon() == 0:
5036             finish(FWON)
5037         if (game.options & OPTION_PLAIN) == 0:
5038             prout(_("Spock-  \"Captain, I believe the `Experimental Death Ray'"))
5039             if withprob(0.05):
5040                 prout(_("   is still operational.\""))
5041             else:
5042                 prout(_("   has been rendered nonfunctional.\""))
5043                 game.damage[DDRAY] = 39.95
5044         return
5045     r = randreal()        # Pick failure method
5046     if r <= 0.30:
5047         prouts(_("Sulu- \"Captain!  It's working!\""))
5048         skip(1)
5049         prouts(_("***RED ALERT!  RED ALERT!"))
5050         skip(1)
5051         prout(_("***MATTER-ANTIMATTER IMPLOSION IMMINENT!"))
5052         skip(1)
5053         prouts(_("***RED ALERT!  RED A*L********************************"))
5054         skip(1)
5055         stars()
5056         prouts(_("******************   KA-BOOM!!!!   *******************"))
5057         skip(1)
5058         kaboom()
5059         return
5060     if r <= 0.55:
5061         prouts(_("Sulu- \"Captain!  Yagabandaghangrapl, brachriigringlanbla!\""))
5062         skip(1)
5063         prout(_("Lt. Uhura-  \"Graaeek!  Graaeek!\""))
5064         skip(1)
5065         prout(_("Spock-  \"Fascinating!  . . . All humans aboard"))
5066         prout(_("  have apparently been transformed into strange mutations."))
5067         prout(_("  Vulcans do not seem to be affected."))
5068         skip(1)
5069         prout(_("Kirk-  \"Raauch!  Raauch!\""))
5070         finish(FDRAY)
5071         return
5072     if r <= 0.75:
5073         prouts(_("Sulu- \"Captain!  It's   --WHAT?!?!\""))
5074         skip(2)
5075         proutn(_("Spock-  \"I believe the word is"))
5076         prouts(_(" *ASTONISHING*"))
5077         prout(_(" Mr. Sulu."))
5078         for i in range(QUADSIZE):
5079             for j in range(QUADSIZE):
5080                 if game.quad[i][j] == '.':
5081                     game.quad[i][j] = '?'
5082         prout(_("  Captain, our quadrant is now infested with"))
5083         prouts(_(" - - - - - -  *THINGS*."))
5084         skip(1)
5085         prout(_("  I have no logical explanation.\""))
5086         return
5087     prouts(_("Sulu- \"Captain!  The Death Ray is creating tribbles!\""))
5088     skip(1)
5089     prout(_("Scotty-  \"There are so many tribbles down here"))
5090     prout(_("  in Engineering, we can't move for 'em, Captain.\""))
5091     finish(FTRIBBLE)
5092     return
5093
5094 # Code from reports.c begins here
5095
5096 def attackreport(curt):
5097     "eport status of bases under attack."
5098     if not curt:
5099         if is_scheduled(FCDBAS):
5100             prout(_("Starbase in Quadrant %s is currently under Commander attack.") % game.battle)
5101             prout(_("It can hold out until Stardate %d.") % int(scheduled(FCDBAS)))
5102         elif game.isatb == 1:
5103             prout(_("Starbase in Quadrant %s is under Super-commander attack.") % game.state.kscmdr)
5104             prout(_("It can hold out until Stardate %d.") % int(scheduled(FSCDBAS)))
5105         else:
5106             prout(_("No Starbase is currently under attack."))
5107     else:
5108         if is_scheduled(FCDBAS):
5109             proutn(_("Base in %s attacked by C. Alive until %.1f") % (game.battle, scheduled(FCDBAS)))
5110         if game.isatb:
5111             proutn(_("Base in %s attacked by S. Alive until %.1f") % (game.state.kscmdr, scheduled(FSCDBAS)))
5112         clreol()
5113
5114 def report():
5115     # report on general game status
5116     scanner.chew()
5117     s1 = (game.thawed and _("thawed ")) or ""
5118     s2 = {1:"short", 2:"medium", 4:"long"}[game.length]
5119     s3 = (None, _("novice"), _("fair"),
5120           _("good"), _("expert"), _("emeritus"))[game.skill]
5121     prout(_("You %s a %s%s %s game.") % ((_("were playing"), _("are playing"))[game.alldone], s1, s2, s3))
5122     if game.skill>SKILL_GOOD and game.thawed and not game.alldone:
5123         prout(_("No plaque is allowed."))
5124     if game.tourn:
5125         prout(_("This is tournament game %d.") % game.tourn)
5126     prout(_("Your secret password is \"%s\"") % game.passwd)
5127     proutn(_("%d of %d Klingons have been killed") % (((game.inkling + game.incom + game.inscom) - game.unwon()),
5128                                                       (game.inkling + game.incom + game.inscom)))
5129     if game.incom - len(game.state.kcmdr):
5130         prout(_(", including %d Commander%s.") % (game.incom - len(game.state.kcmdr), (_("s"), "")[(game.incom - len(game.state.kcmdr))==1]))
5131     elif game.inkling - game.state.remkl + (game.inscom - game.state.nscrem) > 0:
5132         prout(_(", but no Commanders."))
5133     else:
5134         prout(".")
5135     if game.skill > SKILL_FAIR:
5136         prout(_("The Super Commander has %sbeen destroyed.") % ("", _("not "))[game.state.nscrem])
5137     if len(game.state.baseq) != game.inbase:
5138         proutn(_("There "))
5139         if game.inbase-len(game.state.baseq)==1:
5140             proutn(_("has been 1 base"))
5141         else:
5142             proutn(_("have been %d bases") % (game.inbase-len(game.state.baseq)))
5143         prout(_(" destroyed, %d remaining.") % len(game.state.baseq))
5144     else:
5145         prout(_("There are %d bases.") % game.inbase)
5146     if communicating() or game.iseenit:
5147         # Don't report this if not seen and
5148         # either the radio is dead or not at base!
5149         attackreport(False)
5150         game.iseenit = True
5151     if game.casual:
5152         prout(_("%d casualt%s suffered so far.") % (game.casual, ("y", "ies")[game.casual!=1]))
5153     if game.brigcapacity != game.brigfree:
5154         embriggened = brigcapacity-brigfree
5155         if embriggened == 1:
5156             prout(_("1 Klingon in brig"))
5157         else:
5158             prout(_("%d Klingons in brig.") %  embriggened)
5159         if game.kcaptured == 0:
5160             pass
5161         elif game.kcaptured == 1:
5162             prout(_("1 captured Klingon turned in to Starfleet."))
5163         else:
5164             prout(_("%d captured Klingons turned in to Star Fleet.") % game.kcaptured)
5165     if game.nhelp:
5166         prout(_("There were %d call%s for help.") % (game.nhelp,  ("" , _("s"))[game.nhelp!=1]))
5167     if game.ship == 'E':
5168         proutn(_("You have "))
5169         if game.nprobes:
5170             proutn("%d" % (game.nprobes))
5171         else:
5172             proutn(_("no"))
5173         proutn(_(" deep space probe"))
5174         if game.nprobes!=1:
5175             proutn(_("s"))
5176         prout(".")
5177     if communicating() and is_scheduled(FDSPROB):
5178         if game.isarmed:
5179             proutn(_("An armed deep space probe is in "))
5180         else:
5181             proutn(_("A deep space probe is in "))
5182         prout("Quadrant %s." % game.probe.quadrant())
5183     if game.icrystl:
5184         if game.cryprob <= .05:
5185             prout(_("Dilithium crystals aboard ship... not yet used."))
5186         else:
5187             i=0
5188             ai = 0.05
5189             while game.cryprob > ai:
5190                 ai *= 2.0
5191                 i += 1
5192             prout(_("Dilithium crystals have been used %d time%s.") % \
5193                   (i, (_("s"), "")[i==1]))
5194     skip(1)
5195
5196 def lrscan(silent):
5197     "Long-range sensor scan."
5198     if damaged(DLRSENS):
5199         # Now allow base's sensors if docked
5200         if game.condition != "docked":
5201             if not silent:
5202                 prout(_("LONG-RANGE SENSORS DAMAGED."))
5203             return
5204         if not silent:
5205             prout(_("Starbase's long-range scan"))
5206     elif not silent:
5207         prout(_("Long-range scan"))
5208     for x in range(game.quadrant.i-1, game.quadrant.i+2):
5209         if not silent:
5210             proutn(" ")
5211         for y in range(game.quadrant.j-1, game.quadrant.j+2):
5212             if not Coord(x, y).valid_quadrant():
5213                 if not silent:
5214                     proutn("  -1")
5215             else:
5216                 if not damaged(DRADIO):
5217                     game.state.galaxy[x][y].charted = True
5218                 game.state.chart[x][y].klingons = game.state.galaxy[x][y].klingons
5219                 game.state.chart[x][y].starbase = game.state.galaxy[x][y].starbase
5220                 game.state.chart[x][y].stars = game.state.galaxy[x][y].stars
5221                 if not silent and game.state.galaxy[x][y].supernova:
5222                     proutn(" ***")
5223                 elif not silent:
5224                     proutn(" %3d" % (game.state.chart[x][y].klingons*100 + game.state.chart[x][y].starbase * 10 + game.state.chart[x][y].stars))
5225         if not silent:
5226             prout(" ")
5227
5228 def damagereport():
5229     "Damage report."
5230     jdam = False
5231     scanner.chew()
5232     for i in range(NDEVICES):
5233         if damaged(i):
5234             if not jdam:
5235                 prout(_("\tDEVICE\t\t\t-REPAIR TIMES-"))
5236                 prout(_("\t\t\tIN FLIGHT\t\tDOCKED"))
5237                 jdam = True
5238             prout("  %-26s\t%8.2f\t\t%8.2f" % (device[i],
5239                                                game.damage[i]+0.05,
5240                                                DOCKFAC*game.damage[i]+0.005))
5241     if not jdam:
5242         prout(_("All devices functional."))
5243
5244 def rechart():
5245     "Update the chart in the Enterprise's computer from galaxy data."
5246     game.lastchart = game.state.date
5247     for i in range(GALSIZE):
5248         for j in range(GALSIZE):
5249             if game.state.galaxy[i][j].charted:
5250                 game.state.chart[i][j].klingons = game.state.galaxy[i][j].klingons
5251                 game.state.chart[i][j].starbase = game.state.galaxy[i][j].starbase
5252                 game.state.chart[i][j].stars = game.state.galaxy[i][j].stars
5253
5254 def chart():
5255     "Display the star chart."
5256     scanner.chew()
5257     if (game.options & OPTION_AUTOSCAN):
5258         lrscan(silent=True)
5259     if communicating():
5260         rechart()
5261     if game.lastchart < game.state.date and game.condition == "docked":
5262         prout(_("Spock-  \"I revised the Star Chart from the starbase's records.\""))
5263         rechart()
5264     prout(_("       STAR CHART FOR THE KNOWN GALAXY"))
5265     if game.state.date > game.lastchart:
5266         prout(_("(Last surveillance update %d stardates ago).") % ((int)(game.state.date-game.lastchart)))
5267     prout("      1    2    3    4    5    6    7    8")
5268     for i in range(GALSIZE):
5269         proutn("%d |" % (i+1))
5270         for j in range(GALSIZE):
5271             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5272                 proutn("<")
5273             else:
5274                 proutn(" ")
5275             if game.state.galaxy[i][j].supernova:
5276                 show = "***"
5277             elif not game.state.galaxy[i][j].charted and game.state.galaxy[i][j].starbase:
5278                 show = ".1."
5279             elif game.state.galaxy[i][j].charted:
5280                 show = "%3d" % (game.state.chart[i][j].klingons*100 + game.state.chart[i][j].starbase * 10 + game.state.chart[i][j].stars)
5281             else:
5282                 show = "..."
5283             proutn(show)
5284             if (game.options & OPTION_SHOWME) and i == game.quadrant.i and j == game.quadrant.j:
5285                 proutn(">")
5286             else:
5287                 proutn(" ")
5288         proutn("  |")
5289         if i<GALSIZE:
5290             skip(1)
5291
5292 def sectscan(goodScan, i, j):
5293     "Light up an individual dot in a sector."
5294     if goodScan or (abs(i-game.sector.i)<= 1 and abs(j-game.sector.j) <= 1):
5295         if game.quad[i][j] in ('E', 'F'):
5296             if game.iscloaked:
5297                 highvideo()
5298             textcolor({"green":GREEN,
5299                        "yellow":YELLOW,
5300                        "red":RED,
5301                        "docked":CYAN,
5302                        "dead":BROWN}[game.condition])
5303         else:
5304             textcolor({'?':LIGHTMAGENTA,
5305                        'K':LIGHTRED,
5306                        'S':LIGHTRED,
5307                        'C':LIGHTRED,
5308                        'R':LIGHTRED,
5309                        'T':LIGHTRED,
5310                        }.get(game.quad[i][j], DEFAULT))
5311         proutn("%c " % game.quad[i][j])
5312         textcolor(DEFAULT)
5313     else:
5314         proutn("- ")
5315
5316 def status(req=0):
5317     "Emit status report lines"
5318     if not req or req == 1:
5319         prstat(_("Stardate"), _("%.1f, Time Left %.2f") \
5320                % (game.state.date, game.state.remtime))
5321     if not req or req == 2:
5322         if game.condition != "docked":
5323             newcnd()
5324         prstat(_("Condition"), _("%s, %i DAMAGES") % \
5325                (game.condition.upper(), sum([x > 0 for x in game.damage])))
5326         if game.iscloaked:
5327             prout(_(", CLOAKED"))
5328     if not req or req == 3:
5329         prstat(_("Position"), "%s , %s" % (game.quadrant, game.sector))
5330     if not req or req == 4:
5331         if damaged(DLIFSUP):
5332             if game.condition == "docked":
5333                 s = _("DAMAGED, Base provides")
5334             else:
5335                 s = _("DAMAGED, reserves=%4.2f") % game.lsupres
5336         else:
5337             s = _("ACTIVE")
5338         prstat(_("Life Support"), s)
5339     if not req or req == 5:
5340         prstat(_("Warp Factor"), "%.1f" % game.warpfac)
5341     if not req or req == 6:
5342         extra = ""
5343         if game.icrystl and (game.options & OPTION_SHOWME):
5344             extra = _(" (have crystals)")
5345         prstat(_("Energy"), "%.2f%s" % (game.energy, extra))
5346     if not req or req == 7:
5347         prstat(_("Torpedoes"), "%d" % (game.torps))
5348     if not req or req == 8:
5349         if damaged(DSHIELD):
5350             s = _("DAMAGED,")
5351         elif game.shldup:
5352             s = _("UP,")
5353         else:
5354             s = _("DOWN,")
5355         data = _(" %d%% %.1f units") \
5356                % (int((100.0*game.shield)/game.inshld + 0.5), game.shield)
5357         prstat(_("Shields"), s+data)
5358     if not req or req == 9:
5359         prstat(_("Klingons Left"), "%d" % game.unwon())
5360     if not req or req == 10:
5361         if game.options & OPTION_WORLDS:
5362             plnet = game.state.galaxy[game.quadrant.i][game.quadrant.j].planet
5363             if plnet and plnet.inhabited:
5364                 prstat(_("Major system"), plnet.name)
5365             else:
5366                 prout(_("Sector is uninhabited"))
5367     elif not req or req == 11:
5368         attackreport(not req)
5369
5370 def request():
5371     "Request specified status data, a historical relic from slow TTYs."
5372     requests = ("da","co","po","ls","wa","en","to","sh","kl","sy", "ti")
5373     while scanner.nexttok() == "IHEOL":
5374         proutn(_("Information desired? "))
5375     scanner.chew()
5376     if scanner.token in requests:
5377         status(requests.index(scanner.token))
5378     else:
5379         prout(_("UNRECOGNIZED REQUEST. Legal requests are:"))
5380         prout(("  date, condition, position, lsupport, warpfactor,"))
5381         prout(("  energy, torpedoes, shields, klingons, system, time."))
5382
5383 def srscan():
5384     "Short-range scan."
5385     goodScan=True
5386     if damaged(DSRSENS):
5387         # Allow base's sensors if docked
5388         if game.condition != "docked":
5389             prout(_("   S.R. SENSORS DAMAGED!"))
5390             goodScan=False
5391         else:
5392             prout(_("  [Using Base's sensors]"))
5393     else:
5394         prout(_("     Short-range scan"))
5395     if goodScan and communicating():
5396         game.state.chart[game.quadrant.i][game.quadrant.j].klingons = game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons
5397         game.state.chart[game.quadrant.i][game.quadrant.j].starbase = game.state.galaxy[game.quadrant.i][game.quadrant.j].starbase
5398         game.state.chart[game.quadrant.i][game.quadrant.j].stars = game.state.galaxy[game.quadrant.i][game.quadrant.j].stars
5399         game.state.galaxy[game.quadrant.i][game.quadrant.j].charted = True
5400     prout("    1 2 3 4 5 6 7 8 9 10")
5401     if game.condition != "docked":
5402         newcnd()
5403     for i in range(QUADSIZE):
5404         proutn("%2d  " % (i+1))
5405         for j in range(QUADSIZE):
5406             sectscan(goodScan, i, j)
5407         skip(1)
5408
5409 def eta():
5410     "Use computer to get estimated time of arrival for a warp jump."
5411     w1 = Coord(); w2 = Coord()
5412     prompt = False
5413     if damaged(DCOMPTR):
5414         prout(_("COMPUTER DAMAGED, USE A POCKET CALCULATOR."))
5415         skip(1)
5416         return
5417     if scanner.nexttok() != "IHREAL":
5418         prompt = True
5419         scanner.chew()
5420         proutn(_("Destination quadrant and/or sector? "))
5421         if scanner.nexttok()!="IHREAL":
5422             huh()
5423             return
5424     w1.j = int(scanner.real-0.5)
5425     if scanner.nexttok() != "IHREAL":
5426         huh()
5427         return
5428     w1.i = int(scanner.real-0.5)
5429     if scanner.nexttok() == "IHREAL":
5430         w2.j = int(scanner.real-0.5)
5431         if scanner.nexttok() != "IHREAL":
5432             huh()
5433             return
5434         w2.i = int(scanner.real-0.5)
5435     else:
5436         if game.quadrant.j>w1.i:
5437             w2.i = 0
5438         else:
5439             w2.i=QUADSIZE-1
5440         if game.quadrant.i>w1.j:
5441             w2.j = 0
5442         else:
5443             w2.j=QUADSIZE-1
5444     if not w1.valid_quadrant() or not w2.valid_sector():
5445         huh()
5446         return
5447     dist = math.sqrt((w1.j-game.quadrant.j+(w2.j-game.sector.j)/(QUADSIZE*1.0))**2+
5448                      (w1.i-game.quadrant.i+(w2.i-game.sector.i)/(QUADSIZE*1.0))**2)
5449     wfl = False
5450     if prompt:
5451         prout(_("Answer \"no\" if you don't know the value:"))
5452     while True:
5453         scanner.chew()
5454         proutn(_("Time or arrival date? "))
5455         if scanner.nexttok()=="IHREAL":
5456             ttime = scanner.real
5457             if ttime > game.state.date:
5458                 ttime -= game.state.date # Actually a star date
5459             twarp=(math.floor(math.sqrt((10.0*dist)/ttime)*10.0)+1.0)/10.0
5460             if ttime <= 1e-10 or twarp > 10:
5461                 prout(_("We'll never make it, sir."))
5462                 scanner.chew()
5463                 return
5464             if twarp < 1.0:
5465                 twarp = 1.0
5466             break
5467         scanner.chew()
5468         proutn(_("Warp factor? "))
5469         if scanner.nexttok()== "IHREAL":
5470             wfl = True
5471             twarp = scanner.real
5472             if twarp<1.0 or twarp > 10.0:
5473                 huh()
5474                 return
5475             break
5476         prout(_("Captain, certainly you can give me one of these."))
5477     while True:
5478         scanner.chew()
5479         ttime = (10.0*dist)/twarp**2
5480         tpower = dist*twarp*twarp*twarp*(game.shldup+1)
5481         if tpower >= game.energy:
5482             prout(_("Insufficient energy, sir."))
5483             if not game.shldup or tpower > game.energy*2.0:
5484                 if not wfl:
5485                     return
5486                 proutn(_("New warp factor to try? "))
5487                 if scanner.nexttok() == "IHREAL":
5488                     wfl = True
5489                     twarp = scanner.real
5490                     if twarp<1.0 or twarp > 10.0:
5491                         huh()
5492                         return
5493                     continue
5494                 else:
5495                     scanner.chew()
5496                     skip(1)
5497                     return
5498             prout(_("But if you lower your shields,"))
5499             proutn(_("remaining"))
5500             tpower /= 2
5501         else:
5502             proutn(_("Remaining"))
5503         prout(_(" energy will be %.2f.") % (game.energy-tpower))
5504         if wfl:
5505             prout(_("And we will arrive at stardate %.2f.") % (game.state.date+ttime))
5506         elif twarp==1.0:
5507             prout(_("Any warp speed is adequate."))
5508         else:
5509             prout(_("Minimum warp needed is %.2f,") % (twarp))
5510             prout(_("and we will arrive at stardate %.2f.") % (game.state.date+ttime))
5511         if game.state.remtime < ttime:
5512             prout(_("Unfortunately, the Federation will be destroyed by then."))
5513         if twarp > 6.0:
5514             prout(_("You'll be taking risks at that speed, Captain"))
5515         if (game.isatb==1 and game.state.kscmdr == w1 and \
5516              scheduled(FSCDBAS)< ttime+game.state.date) or \
5517             (scheduled(FCDBAS)<ttime+game.state.date and game.battle == w1):
5518             prout(_("The starbase there will be destroyed by then."))
5519         proutn(_("New warp factor to try? "))
5520         if scanner.nexttok() == "IHREAL":
5521             wfl = True
5522             twarp = scanner.real
5523             if twarp<1.0 or twarp > 10.0:
5524                 huh()
5525                 return
5526         else:
5527             scanner.chew()
5528             skip(1)
5529             return
5530
5531 # Code from setup.c begins here
5532
5533 def prelim():
5534     "Issue a historically correct banner."
5535     skip(2)
5536     prout(_("-SUPER- STAR TREK"))
5537     skip(1)
5538 # From the FORTRAN original
5539 #    prout(_("Latest update-21 Sept 78"))
5540 #    skip(1)
5541
5542 def freeze(boss):
5543     "Save game."
5544     if boss:
5545         scanner.push("emsave.trk")
5546     key = scanner.nexttok()
5547     if key == "IHEOL":
5548         proutn(_("File name: "))
5549         key = scanner.nexttok()
5550     if key != "IHALPHA":
5551         huh()
5552         return
5553     if '.' not in scanner.token:
5554         scanner.token += ".trk"
5555     try:
5556         fp = open(scanner.token, "wb")
5557     except IOError:
5558         prout(_("Can't freeze game as file %s") % scanner.token)
5559         return
5560     pickle.dump(game, fp)
5561     fp.close()
5562     scanner.chew()
5563
5564 def thaw():
5565     "Retrieve saved game."
5566     global game
5567     game.passwd = None
5568     key = scanner.nexttok()
5569     if key == "IHEOL":
5570         proutn(_("File name: "))
5571         key = scanner.nexttok()
5572     if key != "IHALPHA":
5573         huh()
5574         return True
5575     if '.' not in scanner.token:
5576         scanner.token += ".trk"
5577     try:
5578         fp = open(scanner.token, "rb")
5579     except IOError:
5580         prout(_("Can't thaw game in %s") % scanner.token)
5581         return
5582     game = pickle.load(fp)
5583     fp.close()
5584     scanner.chew()
5585     return False
5586
5587 # I used <http://www.memory-alpha.org> to find planets
5588 # with references in ST:TOS.  Earth and the Alpha Centauri
5589 # Colony have been omitted.
5590 #
5591 # Some planets marked Class G and P here will be displayed as class M
5592 # because of the way planets are generated. This is a known bug.
5593 systnames = (
5594     # Federation Worlds
5595     _("Andoria (Fesoan)"),        # several episodes
5596     _("Tellar Prime (Miracht)"),        # TOS: "Journey to Babel"
5597     _("Vulcan (T'Khasi)"),        # many episodes
5598     _("Medusa"),                # TOS: "Is There in Truth No Beauty?"
5599     _("Argelius II (Nelphia)"),        # TOS: "Wolf in the Fold" ("IV" in BSD)
5600     _("Ardana"),                # TOS: "The Cloud Minders"
5601     _("Catulla (Cendo-Prae)"),        # TOS: "The Way to Eden"
5602     _("Gideon"),                # TOS: "The Mark of Gideon"
5603     _("Aldebaran III"),                # TOS: "The Deadly Years"
5604     _("Alpha Majoris I"),        # TOS: "Wolf in the Fold"
5605     _("Altair IV"),                # TOS: "Amok Time
5606     _("Ariannus"),                # TOS: "Let That Be Your Last Battlefield"
5607     _("Benecia"),                # TOS: "The Conscience of the King"
5608     _("Beta Niobe I (Sarpeidon)"),        # TOS: "All Our Yesterdays"
5609     _("Alpha Carinae II"),        # TOS: "The Ultimate Computer"
5610     _("Capella IV (Kohath)"),        # TOS: "Friday's Child" (Class G)
5611     _("Daran V"),                # TOS: "For the World is Hollow and I Have Touched the Sky"
5612     _("Deneb II"),                # TOS: "Wolf in the Fold" ("IV" in BSD)
5613     _("Eminiar VII"),                # TOS: "A Taste of Armageddon"
5614     _("Gamma Canaris IV"),        # TOS: "Metamorphosis"
5615     _("Gamma Tranguli VI (Vaalel)"),        # TOS: "The Apple"
5616     _("Ingraham B"),                # TOS: "Operation: Annihilate"
5617     _("Janus IV"),                # TOS: "The Devil in the Dark"
5618     _("Makus III"),                # TOS: "The Galileo Seven"
5619     _("Marcos XII"),                # TOS: "And the Children Shall Lead",
5620     _("Omega IV"),                # TOS: "The Omega Glory"
5621     _("Regulus V"),                # TOS: "Amok Time
5622     _("Deneva"),                # TOS: "Operation -- Annihilate!"
5623     # Worlds from BSD Trek
5624     _("Rigel II"),                # TOS: "Shore Leave" ("III" in BSD)
5625     _("Beta III"),                # TOS: "The Return of the Archons"
5626     _("Triacus"),                # TOS: "And the Children Shall Lead",
5627     _("Exo III"),                # TOS: "What Are Little Girls Made Of?" (Class P)
5628     #        # Others
5629     #    _("Hansen's Planet"),        # TOS: "The Galileo Seven"
5630     #    _("Taurus IV"),                # TOS: "The Galileo Seven" (class G)
5631     #    _("Antos IV (Doraphane)"),        # TOS: "Whom Gods Destroy", "Who Mourns for Adonais?"
5632     #    _("Izar"),                        # TOS: "Whom Gods Destroy"
5633     #    _("Tiburon"),                # TOS: "The Way to Eden"
5634     #    _("Merak II"),                # TOS: "The Cloud Minders"
5635     #    _("Coridan (Desotriana)"),        # TOS: "Journey to Babel"
5636     #    _("Iotia"),                # TOS: "A Piece of the Action"
5637 )
5638
5639 device = (
5640     _("S. R. Sensors"), \
5641     _("L. R. Sensors"), \
5642     _("Phasers"), \
5643     _("Photon Tubes"), \
5644     _("Life Support"), \
5645     _("Warp Engines"), \
5646     _("Impulse Engines"), \
5647     _("Shields"), \
5648     _("Subspace Radio"), \
5649     _("Shuttle Craft"), \
5650     _("Computer"), \
5651     _("Navigation System"), \
5652     _("Transporter"), \
5653     _("Shield Control"), \
5654     _("Death Ray"), \
5655     _("D. S. Probe"), \
5656     _("Cloaking Device"), \
5657 )
5658
5659 def setup():
5660     "Prepare to play, set up cosmos."
5661     w = Coord()
5662     #  Decide how many of everything
5663     if choose():
5664         return # frozen game
5665     # Prepare the Enterprise
5666     game.alldone = game.gamewon = game.shldchg = game.shldup = False
5667     game.ship = 'E'
5668     game.state.crew = FULLCREW
5669     game.energy = game.inenrg = 5000.0
5670     game.shield = game.inshld = 2500.0
5671     game.inlsr = 4.0
5672     game.lsupres = 4.0
5673     game.quadrant = randplace(GALSIZE)
5674     game.sector = randplace(QUADSIZE)
5675     game.torps = game.intorps = 10
5676     game.nprobes = randrange(2, 5)
5677     game.warpfac = 5.0
5678     for i in range(NDEVICES):
5679         game.damage[i] = 0.0
5680     # Set up assorted game parameters
5681     game.battle = Coord()
5682     game.state.date = game.indate = 100.0 * randreal(20, 51)
5683     game.nkinks = game.nhelp = game.casual = game.abandoned = 0
5684     game.iscate = game.resting = game.imine = game.icrystl = game.icraft = False
5685     game.isatb = game.state.nplankl = 0
5686     game.state.starkl = game.state.basekl = game.state.nworldkl = 0
5687     game.iscraft = "onship"
5688     game.landed = False
5689     game.alive = True
5690
5691     # the galaxy
5692     game.state.galaxy = fill2d(GALSIZE, lambda i_unused, j_unused: Quadrant())
5693     # the starchart
5694     game.state.chart = fill2d(GALSIZE, lambda i_unused, j_unused: Page())
5695
5696     game.state.planets = []      # Planet information
5697     game.state.baseq = []      # Base quadrant coordinates
5698     game.state.kcmdr = []      # Commander quadrant coordinates
5699     game.statekscmdr = Coord() # Supercommander quadrant coordinates
5700
5701     # Starchart is functional but we've never seen it
5702     game.lastchart = FOREVER
5703     # Put stars in the galaxy
5704     game.instar = 0
5705     for i in range(GALSIZE):
5706         for j in range(GALSIZE):
5707             # Can't have more stars per quadrant than fit in one decimal digit,
5708             # if we do the chart representation will break.
5709             k = randrange(1, min(10, QUADSIZE**2/10))
5710             game.instar += k
5711             game.state.galaxy[i][j].stars = k
5712     # Locate star bases in galaxy
5713     if game.idebug:
5714         prout("=== Allocating %d bases" % game.inbase)
5715     for i in range(game.inbase):
5716         while True:
5717             while True:
5718                 w = randplace(GALSIZE)
5719                 if not game.state.galaxy[w.i][w.j].starbase:
5720                     break
5721             contflag = False
5722             # C version: for (j = i-1; j > 0; j--)
5723             # so it did them in the opposite order.
5724             for j in range(1, i):
5725                 # Improved placement algorithm to spread out bases
5726                 distq = (w - game.state.baseq[j]).distance()
5727                 if distq < 6.0*(BASEMAX+1-game.inbase) and withprob(0.75):
5728                     contflag = True
5729                     if game.idebug:
5730                         prout("=== Abandoning base #%d at %s" % (i, w))
5731                     break
5732                 elif distq < 6.0 * (BASEMAX+1-game.inbase):
5733                     if game.idebug:
5734                         prout("=== Saving base #%d, close to #%d" % (i, j))
5735             if not contflag:
5736                 break
5737         if game.idebug:
5738             prout("=== Placing base #%d in quadrant %s" % (i, w))
5739         game.state.baseq.append(w)
5740         game.state.galaxy[w.i][w.j].starbase = game.state.chart[w.i][w.j].starbase = True
5741     # Position ordinary Klingon Battle Cruisers
5742     krem = game.inkling
5743     klumper = 0.25*game.skill*(9.0-game.length)+1.0
5744     if klumper > MAXKLQUAD:
5745         klumper = MAXKLQUAD
5746     while True:
5747         r = randreal()
5748         klump = (1.0 - r*r)*klumper
5749         if klump > krem:
5750             klump = krem
5751         krem -= klump
5752         while True:
5753             w = randplace(GALSIZE)
5754             if not game.state.galaxy[w.i][w.j].supernova and \
5755                game.state.galaxy[w.i][w.j].klingons + klump <= MAXKLQUAD:
5756                 break
5757         game.state.galaxy[w.i][w.j].klingons += int(klump)
5758         if krem <= 0:
5759             break
5760     # Position Klingon Commander Ships
5761     for i in range(game.incom):
5762         while True:
5763             w = randplace(GALSIZE)
5764             if not welcoming(w) or w in game.state.kcmdr:
5765                 continue
5766             if (game.state.galaxy[w.i][w.j].klingons or withprob(0.25)):
5767                 break
5768         game.state.galaxy[w.i][w.j].klingons += 1
5769         game.state.kcmdr.append(w)
5770     # Locate planets in galaxy
5771     for i in range(game.inplan):
5772         while True:
5773             w = randplace(GALSIZE)
5774             if game.state.galaxy[w.i][w.j].planet is None:
5775                 break
5776         new = Planet()
5777         new.quadrant = w
5778         new.crystals = "absent"
5779         if (game.options & OPTION_WORLDS) and i < NINHAB:
5780             new.pclass = "M"        # All inhabited planets are class M
5781             new.crystals = "absent"
5782             new.known = "known"
5783             new.name = systnames[i]
5784             new.inhabited = True
5785         else:
5786             new.pclass = ("M", "N", "O")[randrange(0, 3)]
5787             if withprob(0.33):
5788                 new.crystals = "present"
5789             new.known = "unknown"
5790             new.inhabited = False
5791         game.state.galaxy[w.i][w.j].planet = new
5792         game.state.planets.append(new)
5793     # Locate Romulans
5794     for i in range(game.state.nromrem):
5795         w = randplace(GALSIZE)
5796         game.state.galaxy[w.i][w.j].romulans += 1
5797     # Place the Super-Commander if needed
5798     if game.state.nscrem > 0:
5799         while True:
5800             w = randplace(GALSIZE)
5801             if welcoming(w):
5802                 break
5803         game.state.kscmdr = w
5804         game.state.galaxy[w.i][w.j].klingons += 1
5805     # Initialize times for extraneous events
5806     schedule(FSNOVA, expran(0.5 * game.intime))
5807     schedule(FTBEAM, expran(1.5 * (game.intime / len(game.state.kcmdr))))
5808     schedule(FSNAP, randreal(1.0, 2.0)) # Force an early snapshot
5809     schedule(FBATTAK, expran(0.3*game.intime))
5810     unschedule(FCDBAS)
5811     if game.state.nscrem:
5812         schedule(FSCMOVE, 0.2777)
5813     else:
5814         unschedule(FSCMOVE)
5815     unschedule(FSCDBAS)
5816     unschedule(FDSPROB)
5817     if (game.options & OPTION_WORLDS) and game.skill >= SKILL_GOOD:
5818         schedule(FDISTR, expran(1.0 + game.intime))
5819     else:
5820         unschedule(FDISTR)
5821     unschedule(FENSLV)
5822     unschedule(FREPRO)
5823     # Place thing (in tournament game, we don't want one!)
5824     # New in SST2K: never place the Thing near a starbase.
5825     # This makes sense and avoids a special case in the old code.
5826     global thing
5827     if game.tourn is None:
5828         while True:
5829             thing = randplace(GALSIZE)
5830             if thing not in game.state.baseq:
5831                 break
5832     skip(2)
5833     game.state.snap = False
5834     if game.skill == SKILL_NOVICE:
5835         prout(_("It is stardate %d. The Federation is being attacked by") % int(game.state.date))
5836         prout(_("a deadly Klingon invasion force. As captain of the United"))
5837         prout(_("Starship U.S.S. Enterprise, it is your mission to seek out"))
5838         prout(_("and destroy this invasion force of %d battle cruisers.") % ((game.inkling + game.incom + game.inscom)))
5839         prout(_("You have an initial allotment of %d stardates to complete") % int(game.intime))
5840         prout(_("your mission.  As you proceed you may be given more time."))
5841         skip(1)
5842         prout(_("You will have %d supporting starbases.") % (game.inbase))
5843         proutn(_("Starbase locations-  "))
5844     else:
5845         prout(_("Stardate %d.") % int(game.state.date))
5846         skip(1)
5847         prout(_("%d Klingons.") % (game.inkling + game.incom + game.inscom))
5848         prout(_("An unknown number of Romulans."))
5849         if game.state.nscrem:
5850             prout(_("And one (GULP) Super-Commander."))
5851         prout(_("%d stardates.") % int(game.intime))
5852         proutn(_("%d starbases in ") % game.inbase)
5853     for i in range(game.inbase):
5854         proutn(repr(game.state.baseq[i]))
5855         proutn("  ")
5856     skip(2)
5857     proutn(_("The Enterprise is currently in Quadrant %s") % game.quadrant)
5858     proutn(_(" Sector %s") % game.sector)
5859     skip(2)
5860     prout(_("Good Luck!"))
5861     if game.state.nscrem:
5862         prout(_("  YOU'LL NEED IT."))
5863     waitfor()
5864     clrscr()
5865     setwnd(message_window)
5866     newqad()
5867     if len(game.enemies) - (thing == game.quadrant) - (game.tholian != None):
5868         game.shldup = True
5869     if game.neutz:        # bad luck to start in a Romulan Neutral Zone
5870         attack(torps_ok=False)
5871
5872 def choose():
5873     "Choose your game type."
5874     while True:
5875         game.tourn = game.length = 0
5876         game.thawed = False
5877         game.skill = SKILL_NONE
5878         # Do not chew here, we want to use command-line tokens
5879         if not scanner.inqueue: # Can start with command line options
5880             proutn(_("Would you like a regular, tournament, or saved game? "))
5881         scanner.nexttok()
5882         if scanner.sees("tournament"):
5883             while scanner.nexttok() == "IHEOL":
5884                 proutn(_("Type in tournament number-"))
5885             if scanner.real == 0:
5886                 scanner.chew()
5887                 continue # We don't want a blank entry
5888             game.tourn = int(round(scanner.real))
5889             random.seed(scanner.real)
5890             if logfp:
5891                 logfp.write("# random.seed(%d)\n" % scanner.real)
5892             break
5893         if scanner.sees("saved") or scanner.sees("frozen"):
5894             if thaw():
5895                 continue
5896             scanner.chew()
5897             if game.passwd is None:
5898                 continue
5899             if not game.alldone:
5900                 game.thawed = True # No plaque if not finished
5901             report()
5902             waitfor()
5903             return True
5904         if scanner.sees("regular"):
5905             break
5906         proutn(_("What is \"%s\"? ") % scanner.token)
5907         scanner.chew()
5908     while game.length==0 or game.skill==SKILL_NONE:
5909         if scanner.nexttok() == "IHALPHA":
5910             if scanner.sees("short"):
5911                 game.length = 1
5912             elif scanner.sees("medium"):
5913                 game.length = 2
5914             elif scanner.sees("long"):
5915                 game.length = 4
5916             elif scanner.sees("novice"):
5917                 game.skill = SKILL_NOVICE
5918             elif scanner.sees("fair"):
5919                 game.skill = SKILL_FAIR
5920             elif scanner.sees("good"):
5921                 game.skill = SKILL_GOOD
5922             elif scanner.sees("expert"):
5923                 game.skill = SKILL_EXPERT
5924             elif scanner.sees("emeritus"):
5925                 game.skill = SKILL_EMERITUS
5926             else:
5927                 proutn(_("What is \""))
5928                 proutn(scanner.token)
5929                 prout("\"?")
5930         else:
5931             scanner.chew()
5932             if game.length==0:
5933                 proutn(_("Would you like a Short, Medium, or Long game? "))
5934             elif game.skill == SKILL_NONE:
5935                 proutn(_("Are you a Novice, Fair, Good, Expert, or Emeritus player? "))
5936     # Choose game options -- added by ESR for SST2K
5937     if scanner.nexttok() != "IHALPHA":
5938         scanner.chew()
5939         proutn(_("Choose your game style (plain, almy, fancy or just press enter): "))
5940         scanner.nexttok()
5941     if scanner.sees("plain"):
5942         # Approximates the UT FORTRAN version.
5943         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)
5944         game.options |= OPTION_PLAIN
5945     elif scanner.sees("almy"):
5946         # Approximates Tom Almy's version.
5947         game.options &=~ (OPTION_THINGY | OPTION_BLKHOLE | OPTION_BASE | OPTION_WORLDS | OPTION_COLOR)
5948         game.options |= OPTION_ALMY
5949     elif scanner.sees("fancy") or scanner.sees("\n"):
5950         pass
5951     elif len(scanner.token):
5952         proutn(_("What is \"%s\"?") % scanner.token)
5953     setpassword()
5954     if game.passwd == "debug":
5955         game.idebug = True
5956         prout("=== Debug mode enabled.")
5957     # Use parameters to generate initial values of things
5958     game.damfac = 0.5 * game.skill
5959     game.inbase = randrange(BASEMIN, BASEMAX+1)
5960     game.inplan = 0
5961     if game.options & OPTION_PLANETS:
5962         game.inplan += randrange(MAXUNINHAB/2, MAXUNINHAB+1)
5963     if game.options & OPTION_WORLDS:
5964         game.inplan += int(NINHAB)
5965     game.state.nromrem = game.inrom = randrange(2 * game.skill)
5966     game.state.nscrem = game.inscom = (game.skill > SKILL_FAIR)
5967     game.state.remtime = 7.0 * game.length
5968     game.intime = game.state.remtime
5969     game.state.remkl = game.inkling = 2.0*game.intime*((game.skill+1 - 2*randreal())*game.skill*0.1+.15)
5970     game.incom = min(MINCMDR, int(game.skill + 0.0625*game.inkling*randreal()))
5971     game.state.remres = (game.inkling+4*game.incom)*game.intime
5972     game.inresor = game.state.remres
5973     if game.inkling > 50:
5974         game.inbase += 1
5975     return False
5976
5977 def dropin(iquad=None):
5978     "Drop a feature on a random dot in the current quadrant."
5979     while True:
5980         w = randplace(QUADSIZE)
5981         if game.quad[w.i][w.j] == '.':
5982             break
5983     if iquad is not None:
5984         game.quad[w.i][w.j] = iquad
5985     return w
5986
5987 def newcnd():
5988     "Update our alert status."
5989     game.condition = "green"
5990     if game.energy < 1000.0:
5991         game.condition = "yellow"
5992     if game.state.galaxy[game.quadrant.i][game.quadrant.j].klingons or game.state.galaxy[game.quadrant.i][game.quadrant.j].romulans:
5993         game.condition = "red"
5994     if not game.alive:
5995         game.condition="dead"
5996
5997 def newkling():
5998     "Drop new Klingon into current quadrant."
5999     return Enemy('K', loc=dropin(), power=randreal(300,450)+25.0*game.skill)
6000
6001 def sortenemies():
6002     "Sort enemies by distance so 'nearest' is meaningful."
6003     game.enemies.sort(key=lambda x: x.kdist)
6004
6005 def newqad():
6006     "Set up a new state of quadrant, for when we enter or re-enter it."
6007     game.justin = True
6008     game.iplnet = None
6009     game.neutz = game.inorbit = game.landed = False
6010     game.ientesc = game.iseenit = game.isviolreported = False
6011     # Create a blank quadrant
6012     game.quad = fill2d(QUADSIZE, lambda i, j: '.')
6013     if game.iscate:
6014         # Attempt to escape Super-commander, so tbeam back!
6015         game.iscate = False
6016         game.ientesc = True
6017     q = game.state.galaxy[game.quadrant.i][game.quadrant.j]
6018     # cope with supernova
6019     if q.supernova:
6020         return
6021     game.klhere = q.klingons
6022     game.irhere = q.romulans
6023     # Position Starship
6024     game.quad[game.sector.i][game.sector.j] = game.ship
6025     game.enemies = []
6026     if q.klingons:
6027         # Position ordinary Klingons
6028         for _i in range(game.klhere):
6029             newkling()
6030         # If we need a commander, promote a Klingon
6031         for cmdr in game.state.kcmdr:
6032             if cmdr == game.quadrant:
6033                 e = game.enemies[game.klhere-1]
6034                 game.quad[e.location.i][e.location.j] = 'C'
6035                 e.power = randreal(950,1350) + 50.0*game.skill
6036                 break
6037         # If we need a super-commander, promote a Klingon
6038         if game.quadrant == game.state.kscmdr:
6039             e = game.enemies[0]
6040             game.quad[e.location.i][e.location.j] = 'S'
6041             e.power = randreal(1175.0,  1575.0) + 125.0*game.skill
6042             game.iscate = (game.state.remkl > 1)
6043     # Put in Romulans if needed
6044     for _i in range(q.romulans):
6045         Enemy('R', loc=dropin(), power=randreal(400.0,850.0)+50.0*game.skill)
6046     # If quadrant needs a starbase, put it in
6047     if q.starbase:
6048         game.base = dropin('B')
6049     # If quadrant needs a planet, put it in
6050     if q.planet:
6051         game.iplnet = q.planet
6052         if not q.planet.inhabited:
6053             game.plnet = dropin('P')
6054         else:
6055             game.plnet = dropin('@')
6056     # Check for condition
6057     newcnd()
6058     # Check for RNZ
6059     if game.irhere > 0 and game.klhere == 0:
6060         game.neutz = True
6061         if not damaged(DRADIO):
6062             skip(1)
6063             prout(_("LT. Uhura- \"Captain, an urgent message."))
6064             prout(_("  I'll put it on audio.\"  CLICK"))
6065             skip(1)
6066             prout(_("INTRUDER! YOU HAVE VIOLATED THE ROMULAN NEUTRAL ZONE."))
6067             prout(_("LEAVE AT ONCE, OR YOU WILL BE DESTROYED!"))
6068     # Put in THING if needed
6069     if thing == game.quadrant:
6070         Enemy(etype='?', loc=dropin(),
6071               power=randreal(6000,6500.0)+250.0*game.skill)
6072         if not damaged(DSRSENS):
6073             skip(1)
6074             prout(_("Mr. Spock- \"Captain, this is most unusual."))
6075             prout(_("    Please examine your short-range scan.\""))
6076     # Decide if quadrant needs a Tholian; lighten up if skill is low
6077     if game.options & OPTION_THOLIAN:
6078         if (game.skill < SKILL_GOOD and withprob(0.02)) or \
6079             (game.skill == SKILL_GOOD and withprob(0.05)) or \
6080             (game.skill > SKILL_GOOD and withprob(0.08)):
6081             w = Coord()
6082             while True:
6083                 w.i = withprob(0.5) * (QUADSIZE-1)
6084                 w.j = withprob(0.5) * (QUADSIZE-1)
6085                 if game.quad[w.i][w.j] == '.':
6086                     break
6087             game.tholian = Enemy(etype='T', loc=w,
6088                                  power=randrange(100, 500) + 25.0*game.skill)
6089             # Reserve unoccupied corners
6090             if game.quad[0][0]=='.':
6091                 game.quad[0][0] = 'X'
6092             if game.quad[0][QUADSIZE-1]=='.':
6093                 game.quad[0][QUADSIZE-1] = 'X'
6094             if game.quad[QUADSIZE-1][0]=='.':
6095                 game.quad[QUADSIZE-1][0] = 'X'
6096             if game.quad[QUADSIZE-1][QUADSIZE-1]=='.':
6097                 game.quad[QUADSIZE-1][QUADSIZE-1] = 'X'
6098     sortenemies()
6099     # And finally the stars
6100     for _i in range(q.stars):
6101         dropin('*')
6102     # Put in a few black holes
6103     for _i in range(1, 3+1):
6104         if withprob(0.5):
6105             dropin(' ')
6106     # Take out X's in corners if Tholian present
6107     if game.tholian:
6108         if game.quad[0][0]=='X':
6109             game.quad[0][0] = '.'
6110         if game.quad[0][QUADSIZE-1]=='X':
6111             game.quad[0][QUADSIZE-1] = '.'
6112         if game.quad[QUADSIZE-1][0]=='X':
6113             game.quad[QUADSIZE-1][0] = '.'
6114         if game.quad[QUADSIZE-1][QUADSIZE-1]=='X':
6115             game.quad[QUADSIZE-1][QUADSIZE-1] = '.'
6116
6117 def setpassword():
6118     "Set the self-destruct password."
6119     if game.options & OPTION_PLAIN:
6120         while True:
6121             scanner.chew()
6122             proutn(_("Please type in a secret password- "))
6123             scanner.nexttok()
6124             game.passwd = scanner.token
6125             if game.passwd != None:
6126                 break
6127     else:
6128         game.passwd = ""
6129         game.passwd += chr(ord('a')+randrange(26))
6130         game.passwd += chr(ord('a')+randrange(26))
6131         game.passwd += chr(ord('a')+randrange(26))
6132
6133 # Code from sst.c begins here
6134
6135 commands = [
6136     ("SRSCAN",           OPTION_TTY),
6137     ("STATUS",           OPTION_TTY),
6138     ("REQUEST",          OPTION_TTY),
6139     ("LRSCAN",           OPTION_TTY),
6140     ("PHASERS",          0),
6141     ("TORPEDO",          0),
6142     ("PHOTONS",          0),
6143     ("MOVE",             0),
6144     ("SHIELDS",          0),
6145     ("DOCK",             0),
6146     ("DAMAGES",          0),
6147     ("CHART",            0),
6148     ("IMPULSE",          0),
6149     ("REST",             0),
6150     ("WARP",             0),
6151     ("SENSORS",          OPTION_PLANETS),
6152     ("ORBIT",            OPTION_PLANETS),
6153     ("TRANSPORT",        OPTION_PLANETS),
6154     ("MINE",             OPTION_PLANETS),
6155     ("CRYSTALS",         OPTION_PLANETS),
6156     ("SHUTTLE",          OPTION_PLANETS),
6157     ("PLANETS",          OPTION_PLANETS),
6158     ("REPORT",           0),
6159     ("COMPUTER",         0),
6160     ("COMMANDS",         0),
6161     ("EMEXIT",           0),
6162     ("PROBE",            OPTION_PROBE),
6163     ("SAVE",             0),
6164     ("FREEZE",           0),        # Synonym for SAVE
6165     ("ABANDON",          0),
6166     ("DESTRUCT",         0),
6167     ("DEATHRAY",         0),
6168     ("CAPTURE",          OPTION_CAPTURE),
6169     ("CLOAK",            OPTION_CLOAK),
6170     ("DEBUG",            0),
6171     ("MAYDAY",           0),
6172     ("SOS",              0),        # Synonym for MAYDAY
6173     ("CALL",             0),        # Synonym for MAYDAY
6174     ("QUIT",             0),
6175     ("HELP",             0),
6176     ("SCORE",            0),
6177     ("",                 0),
6178 ]
6179
6180 def listCommands():
6181     "Generate a list of legal commands."
6182     prout(_("LEGAL COMMANDS ARE:"))
6183     emitted = 0
6184     for (key, opt) in commands:
6185         if not opt or (opt & game.options):
6186             proutn("%-12s " % key)
6187             emitted += 1
6188             if emitted % 5 == 4:
6189                 skip(1)
6190     skip(1)
6191
6192 def helpme():
6193     "Browse on-line help."
6194     key = scanner.nexttok()
6195     while True:
6196         if key == "IHEOL":
6197             setwnd(prompt_window)
6198             proutn(_("Help on what command? "))
6199             key = scanner.nexttok()
6200         setwnd(message_window)
6201         if key == "IHEOL":
6202             return
6203         cmds = [x[0] for x in commands]
6204         if scanner.token.upper() in cmds or scanner.token.upper() == "ABBREV":
6205             break
6206         skip(1)
6207         listCommands()
6208         key = "IHEOL"
6209         scanner.chew()
6210         skip(1)
6211     cmd = scanner.token.upper()
6212     for directory in docpath:
6213         try:
6214             fp = open(os.path.join(directory, "sst.doc"), "r")
6215             break
6216         except IOError:
6217             pass
6218     else:
6219         prout(_("Spock-  \"Captain, that information is missing from the"))
6220         prout(_("   computer. You need to find sst.doc and put it somewhere"))
6221         proutn(_("   in these directories: %s") % ":".join(docpath))
6222         prout(".\"")
6223         # This used to continue: "You need to find SST.DOC and put
6224         # it in the current directory."
6225         return
6226     while True:
6227         linebuf = fp.readline()
6228         if linebuf == '':
6229             prout(_("Spock- \"Captain, there is no information on that command.\""))
6230             fp.close()
6231             return
6232         if linebuf[0] == '%' and linebuf[1] == '%' and linebuf[2] == ' ':
6233             linebuf = linebuf[3:].strip()
6234             if cmd.upper() == linebuf:
6235                 break
6236     skip(1)
6237     prout(_("Spock- \"Captain, I've found the following information:\""))
6238     skip(1)
6239     while True:
6240         linebuf = fp.readline()
6241         if "******" in linebuf:
6242             break
6243         proutn(linebuf)
6244     fp.close()
6245
6246 def makemoves():
6247     "Command-interpretation loop."
6248     def checkviol():
6249         if game.irhere and game.state.date >= ALGERON and not game.isviolreported and game.iscloaked:
6250             prout(_("The Romulan ship discovers you are breaking the Treaty of Algeron!"))
6251             game.ncviol += 1
6252             game.isviolreported = True
6253     while True:         # command loop
6254         drawmaps(1)
6255         while True:        # get a command
6256             hitme = False
6257             game.optime = game.justin = False
6258             scanner.chew()
6259             setwnd(prompt_window)
6260             clrscr()
6261             proutn("COMMAND> ")
6262             if scanner.nexttok() == "IHEOL":
6263                 if game.options & OPTION_CURSES:
6264                     makechart()
6265                 continue
6266             elif scanner.token == "":
6267                 continue
6268             game.ididit = False
6269             clrscr()
6270             setwnd(message_window)
6271             clrscr()
6272             abandon_passed = False
6273             cmd = ""    # Force cmd to persist after loop
6274             opt = 0     # Force opt to persist after loop
6275             for (cmd, opt) in commands:
6276                 # commands after ABANDON cannot be abbreviated
6277                 if cmd == "ABANDON":
6278                     abandon_passed = True
6279                 if cmd == scanner.token.upper() or (not abandon_passed \
6280                         and cmd.startswith(scanner.token.upper())):
6281                     break
6282             if cmd == "":
6283                 listCommands()
6284                 continue
6285             elif opt and not (opt & game.options):
6286                 huh()
6287             else:
6288                 break
6289         prout("COMMAND> %s" % cmd)
6290         if cmd == "SRSCAN":                # srscan
6291             srscan()
6292         elif cmd == "STATUS":                # status
6293             status()
6294         elif cmd == "REQUEST":                # status request
6295             request()
6296         elif cmd == "LRSCAN":                # long range scan
6297             lrscan(silent=False)
6298         elif cmd == "PHASERS":                # phasers
6299             phasers()
6300             if game.ididit:
6301                 checkviol()
6302                 hitme = True
6303         elif cmd in ("TORPEDO", "PHOTONS"):        # photon torpedos
6304             torps()
6305             if game.ididit:
6306                 checkviol()
6307                 hitme = True
6308         elif cmd == "MOVE":                # move under warp
6309             warp(wcourse=None, involuntary=False)
6310         elif cmd == "SHIELDS":                # shields
6311             doshield(shraise=False)
6312             if game.ididit:
6313                 hitme = True
6314                 game.shldchg = False
6315         elif cmd == "DOCK":                # dock at starbase
6316             dock(True)
6317             if game.ididit:
6318                 attack(torps_ok=False)
6319         elif cmd == "DAMAGES":                # damage reports
6320             damagereport()
6321         elif cmd == "CHART":                # chart
6322             makechart()
6323         elif cmd == "IMPULSE":                # impulse
6324             impulse()
6325         elif cmd == "REST":                # rest
6326             wait()
6327             if game.ididit:
6328                 hitme = True
6329         elif cmd == "WARP":                # warp
6330             setwarp()
6331         elif cmd == "SENSORS":                # sensors
6332             sensor()
6333         elif cmd == "ORBIT":                # orbit
6334             orbit()
6335             if game.ididit:
6336                 hitme = True
6337         elif cmd == "TRANSPORT":                # transport "beam"
6338             beam()
6339         elif cmd == "MINE":                # mine
6340             mine()
6341             if game.ididit:
6342                 hitme = True
6343         elif cmd == "CRYSTALS":                # crystals
6344             usecrystals()
6345             if game.ididit:
6346                 hitme = True
6347         elif cmd == "SHUTTLE":                # shuttle
6348             shuttle()
6349             if game.ididit:
6350                 hitme = True
6351         elif cmd == "PLANETS":                # Planet list
6352             survey()
6353         elif cmd == "REPORT":                # Game Report
6354             report()
6355         elif cmd == "COMPUTER":                # use COMPUTER!
6356             eta()
6357         elif cmd == "COMMANDS":
6358             listCommands()
6359         elif cmd == "EMEXIT":                # Emergency exit
6360             clrscr()                        # Hide screen
6361             freeze(True)                # forced save
6362             raise SystemExit(1)                # And quick exit
6363         elif cmd == "PROBE":
6364             probe()                        # Launch probe
6365             if game.ididit:
6366                 hitme = True
6367         elif cmd == "ABANDON":                # Abandon Ship
6368             abandon()
6369         elif cmd == "DESTRUCT":                # Self Destruct
6370             selfdestruct()
6371         elif cmd == "SAVE":                # Save Game
6372             freeze(False)
6373             clrscr()
6374             if game.skill > SKILL_GOOD:
6375                 prout(_("WARNING--Saved games produce no plaques!"))
6376         elif cmd == "DEATHRAY":                # Try a desparation measure
6377             deathray()
6378             if game.ididit:
6379                 hitme = True
6380         elif cmd == "CAPTURE":
6381             capture()
6382         elif cmd == "CLOAK":
6383             cloak()
6384         elif cmd == "DEBUGCMD":                # What do we want for debug???
6385             debugme()
6386         elif cmd == "MAYDAY":                # Call for help
6387             mayday()
6388             if game.ididit:
6389                 hitme = True
6390         elif cmd == "QUIT":
6391             game.alldone = True                # quit the game
6392         elif cmd == "HELP":
6393             helpme()                        # get help
6394         elif cmd == "SCORE":
6395             score()                         # see current score
6396         while True:
6397             if game.alldone:
6398                 break                # Game has ended
6399             if game.optime != 0.0:
6400                 events()
6401                 if game.alldone:
6402                     break        # Events did us in
6403             if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6404                 atover(False)
6405                 continue
6406             if hitme and not game.justin:
6407                 attack(torps_ok=True)
6408                 if game.alldone:
6409                     break
6410                 if game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova:
6411                     atover(False)
6412                     hitme = True
6413                     continue
6414             break
6415         if game.alldone:
6416             break
6417     if game.idebug:
6418         prout("=== Ending")
6419
6420 def cramen(ch):
6421     "Emit the name of an enemy or feature."
6422     if   ch == 'R': s = _("Romulan")
6423     elif ch == 'K': s = _("Klingon")
6424     elif ch == 'C': s = _("Commander")
6425     elif ch == 'S': s = _("Super-commander")
6426     elif ch == '*': s = _("Star")
6427     elif ch == 'P': s = _("Planet")
6428     elif ch == 'B': s = _("Starbase")
6429     elif ch == ' ': s = _("Black hole")
6430     elif ch == 'T': s = _("Tholian")
6431     elif ch == '#': s = _("Tholian web")
6432     elif ch == '?': s = _("Stranger")
6433     elif ch == '@': s = _("Inhabited World")
6434     else: s = "Unknown??"
6435     return s
6436
6437 def crmena(loud, enemy, loctype, w):
6438     "Emit the name of an enemy and his location."
6439     buf = ""
6440     if loud:
6441         buf += "***"
6442     buf += cramen(enemy) + _(" at ")
6443     if loctype == "quadrant":
6444         buf += _("Quadrant ")
6445     elif loctype == "sector":
6446         buf += _("Sector ")
6447     return buf + repr(w)
6448
6449 def crmshp():
6450     "Emit our ship name."
6451     return{'E':_("Enterprise"),'F':_("Faerie Queene")}.get(game.ship,"Ship???")
6452
6453 def stars():
6454     "Emit a line of stars"
6455     prouts("******************************************************")
6456     skip(1)
6457
6458 def expran(avrage):
6459     return -avrage*math.log(1e-7 + randreal())
6460
6461 def randplace(size):
6462     "Choose a random location."
6463     w = Coord()
6464     w.i = randrange(size)
6465     w.j = randrange(size)
6466     return w
6467
6468 class sstscanner:
6469     def __init__(self):
6470         self.type = None
6471         self.token = None
6472         self.real = 0.0
6473         self.inqueue = []
6474     def nexttok(self):
6475         # Get a token from the user
6476         self.real = 0.0
6477         self.token = ''
6478         # Fill the token quue if nothing here
6479         while not self.inqueue:
6480             sline = cgetline()
6481             if curwnd==prompt_window:
6482                 clrscr()
6483                 setwnd(message_window)
6484                 clrscr()
6485             if sline == '':
6486                 return None
6487             if not sline:
6488                 continue
6489             else:
6490                 self.inqueue = sline.lstrip().split() + ["\n"]
6491         # From here on in it's all looking at the queue
6492         self.token = self.inqueue.pop(0)
6493         if self.token == "\n":
6494             self.type = "IHEOL"
6495             return "IHEOL"
6496         try:
6497             self.real = float(self.token)
6498             self.type = "IHREAL"
6499             return "IHREAL"
6500         except ValueError:
6501             pass
6502         # Treat as alpha
6503         self.token = self.token.lower()
6504         self.type = "IHALPHA"
6505         self.real = None
6506         return "IHALPHA"
6507     def append(self, tok):
6508         self.inqueue.append(tok)
6509     def push(self, tok):
6510         self.inqueue.insert(0, tok)
6511     def waiting(self):
6512         return self.inqueue
6513     def chew(self):
6514         # Demand input for next scan
6515         self.inqueue = []
6516         self.real = self.token = None
6517     def sees(self, s):
6518         # compares s to item and returns true if it matches to the length of s
6519         return s.startswith(self.token)
6520     def int(self):
6521         # Round token value to nearest integer
6522         return int(round(self.real))
6523     def getcoord(self):
6524         s = Coord()
6525         self.nexttok()
6526         if self.type != "IHREAL":
6527             huh()
6528             return None
6529         s.i = self.int()-1
6530         self.nexttok()
6531         if self.type != "IHREAL":
6532             huh()
6533             return None
6534         s.j = self.int()-1
6535         return s
6536     def __repr__(self):
6537         return "<sstcanner: token=%s, type=%s, queue=%s>" % (self.token, self.type, self.inqueue)
6538
6539 def ja():
6540     "Yes-or-no confirmation."
6541     scanner.chew()
6542     while True:
6543         scanner.nexttok()
6544         if scanner.token == 'y':
6545             return True
6546         if scanner.token == 'n':
6547             return False
6548         scanner.chew()
6549         proutn(_("Please answer with \"y\" or \"n\": "))
6550
6551 def huh():
6552     "Complain about unparseable input."
6553     scanner.chew()
6554     skip(1)
6555     prout(_("Beg your pardon, Captain?"))
6556
6557 def debugme():
6558     "Access to the internals for debugging."
6559     proutn("Reset levels? ")
6560     if ja():
6561         if game.energy < game.inenrg:
6562             game.energy = game.inenrg
6563         game.shield = game.inshld
6564         game.torps = game.intorps
6565         game.lsupres = game.inlsr
6566     proutn("Reset damage? ")
6567     if ja():
6568         for i in range(NDEVICES):
6569             if game.damage[i] > 0.0:
6570                 game.damage[i] = 0.0
6571     proutn("Toggle debug flag? ")
6572     if ja():
6573         game.idebug = not game.idebug
6574         if game.idebug:
6575             prout("Debug output ON")
6576         else:
6577             prout("Debug output OFF")
6578     proutn("Cause selective damage? ")
6579     if ja():
6580         for i in range(NDEVICES):
6581             proutn("Kill %s?" % device[i])
6582             scanner.chew()
6583             key = scanner.nexttok()
6584             if key == "IHALPHA" and scanner.sees("y"):
6585                 game.damage[i] = 10.0
6586     proutn("Examine/change events? ")
6587     if ja():
6588         ev = Event()
6589         w = Coord()
6590         legends = {
6591             FSNOVA:  "Supernova       ",
6592             FTBEAM:  "T Beam          ",
6593             FSNAP:   "Snapshot        ",
6594             FBATTAK: "Base Attack     ",
6595             FCDBAS:  "Base Destroy    ",
6596             FSCMOVE: "SC Move         ",
6597             FSCDBAS: "SC Base Destroy ",
6598             FDSPROB: "Probe Move      ",
6599             FDISTR:  "Distress Call   ",
6600             FENSLV:  "Enslavement     ",
6601             FREPRO:  "Klingon Build   ",
6602         }
6603         for i in range(1, NEVENTS):
6604             proutn(legends[i])
6605             if is_scheduled(i):
6606                 proutn("%.2f" % (scheduled(i)-game.state.date))
6607                 if i == FENSLV or i == FREPRO:
6608                     ev = findevent(i)
6609                     proutn(" in %s" % ev.quadrant)
6610             else:
6611                 proutn("never")
6612             proutn("? ")
6613             scanner.chew()
6614             key = scanner.nexttok()
6615             if key == 'n':
6616                 unschedule(i)
6617                 scanner.chew()
6618             elif key == "IHREAL":
6619                 ev = schedule(i, scanner.real)
6620                 if i == FENSLV or i == FREPRO:
6621                     scanner.chew()
6622                     proutn("In quadrant- ")
6623                     key = scanner.nexttok()
6624                     # "IHEOL" says to leave coordinates as they are
6625                     if key != "IHEOL":
6626                         if key != "IHREAL":
6627                             prout("Event %d canceled, no x coordinate." % (i))
6628                             unschedule(i)
6629                             continue
6630                         w.i = int(round(scanner.real))
6631                         key = scanner.nexttok()
6632                         if key != "IHREAL":
6633                             prout("Event %d canceled, no y coordinate." % (i))
6634                             unschedule(i)
6635                             continue
6636                         w.j = int(round(scanner.real))
6637                         ev.quadrant = w
6638         scanner.chew()
6639     proutn("Induce supernova here? ")
6640     if ja():
6641         game.state.galaxy[game.quadrant.i][game.quadrant.j].supernova = True
6642         atover(True)
6643
6644 if __name__ == '__main__':
6645     try:
6646         #global line, thing, game
6647         game = None
6648         thing = Thingy()
6649         game = Gamestate()
6650         game.options = OPTION_ALL &~ (OPTION_IOMODES | OPTION_PLAIN | OPTION_ALMY)
6651         if os.getenv("TERM"):
6652             game.options |= OPTION_CURSES
6653         else:
6654             game.options |= OPTION_TTY
6655         seed = int(time.time())
6656         (options, arguments) = getopt.getopt(sys.argv[1:], "r:s:txV")
6657         replay = False
6658         for (switch, val) in options:
6659             if switch == '-r':
6660                 try:
6661                     replayfp = open(val, "r")
6662                 except IOError:
6663                     sys.stderr.write("sst: can't open replay file %s\n" % val)
6664                     raise SystemExit(1)
6665                 try:
6666                     line = replayfp.readline().strip()
6667                     (leader, __, seed) = line.split()
6668                     seed = eval(seed)
6669                     sys.stderr.write("sst2k: seed set to %s\n" % seed)
6670                     line = replayfp.readline().strip()
6671                     arguments += line.split()[2:]
6672                     replay = True
6673                 except ValueError:
6674                     sys.stderr.write("sst: replay file %s is ill-formed\n"% val)
6675                     raise SystemExit(1)
6676                 game.options |= OPTION_TTY
6677                 game.options &=~ OPTION_CURSES
6678             elif switch == '-s':
6679                 seed = int(val)
6680             elif switch == '-t':
6681                 game.options |= OPTION_TTY
6682                 game.options &=~ OPTION_CURSES
6683             elif switch == '-x':
6684                 game.idebug = True
6685             elif switch == '-V':
6686                 print("SST2K", version)
6687                 raise SystemExit(0)
6688             else:
6689                 sys.stderr.write("usage: sst [-t] [-x] [startcommand...].\n")
6690                 raise SystemExit(1)
6691         # where to save the input in case of bugs
6692         if "TMPDIR" in os.environ:
6693             tmpdir = os.environ['TMPDIR']
6694         else:
6695             tmpdir = "/tmp"
6696         try:
6697             logfp = open(os.path.join(tmpdir, "sst-input.log"), "w")
6698         except IOError:
6699             sys.stderr.write("sst: warning, can't open logfile\n")
6700             sys.exit(1)
6701         if logfp:
6702             logfp.write("# seed %s\n" % seed)
6703             logfp.write("# options %s\n" % " ".join(arguments))
6704             logfp.write("# SST2K version %s\n" % version)
6705             logfp.write("# recorded by %s@%s on %s\n" % \
6706                     (getpass.getuser(),socket.gethostname(),time.ctime()))
6707         random.seed(seed)
6708         scanner = sstscanner()
6709         for arg in arguments:
6710             scanner.append(arg)
6711         try:
6712             iostart()
6713             while True: # Play a game
6714                 setwnd(fullscreen_window)
6715                 clrscr()
6716                 prelim()
6717                 setup()
6718                 if game.alldone:
6719                     score()
6720                     game.alldone = False
6721                 else:
6722                     makemoves()
6723                 if replay:
6724                     break
6725                 skip(1)
6726                 stars()
6727                 skip(1)
6728                 if game.tourn and game.alldone:
6729                     proutn(_("Do you want your score recorded?"))
6730                     if ja():
6731                         scanner.chew()
6732                         scanner.push("\n")
6733                         freeze(False)
6734                 scanner.chew()
6735                 proutn(_("Do you want to play again? "))
6736                 if not ja():
6737                     break
6738             skip(1)
6739             prout(_("May the Great Bird of the Galaxy roost upon your home planet."))
6740         finally:
6741             ioend()
6742         raise SystemExit(0)
6743     except KeyboardInterrupt:
6744         if logfp:
6745             logfp.close()
6746         print("")
6747
6748 # End.