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