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