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